「JLOI2015」骗我呢
作者:互联网
由题意,每一行内的数单调递增。又因为 \(0 \leqslant a_{i,j} \leqslant m\) 限制了这些数的取值范围。
那么我们相当于在 \(m + 1\) 个数中选 \(m\) 个数。必然有两个之间相差 \(2\),其余的数连续。
我们设 \(f_{i,j}\) 表示第 \(i\) 行中,被舍弃掉的数是 \(j\) 的方案数。
那么则有
\[\begin{aligned} f_{i,j} &= \sum_{k=0}^{j+1} f_{i-1,k} \\ &=\sum_{k=0}^{j} f_{i-1,k} + f_{i-1,j+1} \\ &= f_{i,j-1} + f_{i-1,j+1} \end{aligned} \]这个式子看上去是一个表格中的递推,我们将其画到网格图中(第 \(i\) 行第 \(j\) 列):
这个样子很丑,我们尽量将转移变成水平/垂直的。
虚线为原本的转移,将其改成向上再向右。
这样就有一点像计数问题了。
我们在两侧加上两条直线:
不难发现,结果即为从 \((0,0)\) 到 \((n+m,n)\) ,不经过直线 \(A:y=x+1\) 和 \(B:y=x-m-2\) 的路径总数。
考虑用组合数算这个东西。我们定义由 \(A\) 和 \(B\) 组成的序列表示先经过 \(A\) 或 \(B\) 的方案。如 \(AABABBAB\)。
我们发现,连续经过同一条直线没有影响,所以把所有的相同部分全部缩成一个。
显然,它要么以 \(A\) 开头,要么以 \(B\) 开头。
我们直接用总方案数 ( \(\tbinom{2n+m+1}{n}\) ) - \(A\) 开头的方案数 - \(B\) 开头的方案数。
但是后两者又该怎么计算呢?
我们使用对称。
我们将直线 \(B\) 关于直线 \(A\) 对称过去。过 \(A\) 再过 \(B\) 实质上就是不停的跨越直线的过程。我们一直对称下去。当我们发现,对称过去的某个点在回来之后并不属于第一象限了,说明这条路径不合法。
容斥。\(A\) 开头相当于 \(A+AB-BA-BAB+ABA+ABAB \cdots\)。 \(B\) 开头同理。
我们每次对称都必然会使得距离加一,所以最终一定能找到答案。
#include <cstdio>
const int ccf = 1e9 + 7, N = 3e6 + 5;
int n, m, nn, fac[N], inv[N];
void init() {
fac[0] = fac[1] = inv[0] = inv[1] = 1;
nn = n * 2 + m + 1;
for (int i = 2; i <= nn; i++) fac[i] = 1ll * fac[i - 1] * i % ccf;
for (int i = 2; i <= nn; i++) inv[i] = 1ll * (ccf - ccf / i) * inv[ccf % i] % ccf;
for (int i = 2; i <= nn; i++) inv[i] = 1ll * inv[i] * inv[i - 1] % ccf;
}
int C(int n, int m) { return 1ll * fac[n] * inv[m] % ccf * inv[n - m] % ccf; }
int Mir(int x, int y, int dir) {
if (x < 0 || y < 0) return 0;
int delta = dir ? -m - 2 : 1;
return (C(x + y, y) - Mir(y + delta, x - delta, dir ^ 1) + ccf) % ccf;
}
int main(void) {
scanf("%d%d", &n, &m);
init();
int ans = C(nn, n);
ans = (ans - Mir(n - 1, n + m + 2, 0) + ccf) % ccf;
ans = (ans - Mir(n + m + 2, n - 1, 1) + ccf) % ccf;
printf("%d\n", ans);
return 0;
}
标签:直线,JLOI2015,int,开头,对称,fac,我们 来源: https://www.cnblogs.com/Lewis-Li/p/JLOI2015_pwn.html