【YBT2023寒假Day5 C】路径计数(数学)(生成函数)
路径计数
题目链接:YBT2023寒假Day5 C
题目大意
有一个 h*w 的网格,你要从左上角走到右下角,每次可以向右或者向下,定义一条路径的分数是它经过的位置的权值和。
每次会选择权值大的点走过去,如果权值相同,就走右边,如果只有一个方向可以走就走那个方向。
一直左上角权值为 0,其它格子值域 0~S,问你有多少个网格满足路径的分数恰好是 S。
思路
考虑到网格中有很多的地方是没有限制的,而这个数量你会发现并不是固定的。
考虑到移动可以因此分为几类。
(没有限制的向右走,没有限制的向下走,有限制的往限制的方向走)
不过会注意到其实最后会有限制,但两种限制其实不同,考虑分别求贡献加起来。
假设接触了下边界,碰的位置是 \((h,a+1)\),也就是已经往右走了 \(a\) 次。
那往下走的次数 \(b=h-1\),最后限制的走要走 \(c=w-1-a\) 次。
考虑到没有限制的操作会固定一个格子的值,限制一个格子的取值,而有限制的操作会固定一个格子的值。
那任意填数的格子数 \(d=hw-1-2a-2b-c\)
然后考虑每种操作分开来 DP 或者什么的,就比如无限制往右走,那就是 \(f_{i,j}\) 为进行 \(i\) 次这个操作,权值和是 \(j\) 的方案数。
(这里就是把 \(j\) 拆成 \(i\) 个非负整数的所有方案,这 \(i\) 个数各自加一的乘积之和)
(加一是因为相等的时候往右走,可以相等)
那无限制往右走就是 \(g_{i,j}\) 为把 \(j\) 拆成 \(i\) 个非负整数的所有方案,这些数的乘积之和。
而有限制的,就是 \(h_{i,j}\) 为把 \(j\) 拆成 \(i\) 个非负整数的方案数。
那总答案就是:(注意组合数是 \(\binom{a+b-1}{a}\) 是因为最后一步一定是 \(b\))
\((S+1)^d\binom{a+b-1}{a}\sum\limits_{x+y\leqslant S}f_{a,x}g_{b,y}h_{c,S-x-y}\)
但是这还不够,考虑后面枚举的部分要怎么处理。
考虑到这个 DP 都很简单,考虑用看看转移式:
\(f_{i,j}=\sum\limits_{k=0}^jf_{i-1,j-k}(k+1)\)
\(g_{i,j}=\sum\limits_{k=0}^{j}f_{i-1,j-k}k\)
\(h_{i,j}=\sum\limits_{k=0}^jf_{i-1,j-k}\)
发现是卷积的形式。
然后我们尝试生成函数:
\(F(x)=\sum\limits_{i\geqslant 0}(i+1)x^i\)
\(G(x)=\sum\limits_{i\geqslant 0}ix^i\)
\(H(x)=\sum\limits_{i\geqslant 0}x^i\)
然后弄封闭形式:(错位相减,就只写 \(F(x)\) 的例子了)
\(xF(x)=\sum\limits_{i\geqslant 0}(i+1)x^{i+1}=\sum\limits_{i\geqslant 1}ix^i\)
\(F(x)-xF(x)=1+\sum\limits_{i\geqslant 1}x^i=\sum\limits_{i\geqslant 0}x^i\)
\(F(x)=\dfrac{\sum\limits_{i\geqslant 0}x^i}{1-x}\)
然后这个是 \(H(x)\),然后同样的道理(或者这个经典的形式),就得到 \(H(x)=\dfrac{1}{1-x}\)
那 \(F(x)=\dfrac{1}{(1-x)^2}\),\(G(x)\) 同理有 \(G(x)=\dfrac{x}{(1-x)^2}\)
然后放进后面枚举的式子里面:
\(\sum\limits_{x+y\leqslant S}f_{a,x}g_{b,y}h_{c,S-x-y}\)
其实就是要:
\([x^S](F^aG^bH^c)(x)=[x^S]\dfrac{x^b}{(1-x)^{2a+2b+c}}=[x^{S-b}]\dfrac{1}{(1-x)^{2a+2b+c}}\)
不过 \(\dfrac{1}{1-x}\) 是 \(\sum\limits_{i\geqslant 0}x^i\) 的封闭形式,那这个就是 \(2a+2b+c\) 个卷积起来。
那在组合意义上,它的 \(x^{S-b}\) 项,就是用 \(2a+2b+c\) 个非负整数组成 \(S-b\) 的方案数。
那这个我们用插板法,就是 \(\binom{S-b+2a+2b+c-1}{2a+2b+c-1}\)
代码
#include<cstdio>
#define ll long long
#define mo 1000000007
using namespace std;
const int N = 8e6;
int n, m, s, jc[N], inv[N], invs[N];
int add(int x, int y) {return x + y >= mo ? x + y - mo : x + y;}
int dec(int x, int y) {return x > y ? x - y : x - y + mo;}
int mul(int x, int y) {return 1ll * x * y % mo;}
int ksm(int x, ll y) {
int re = 1;
while (y) {
if (y & 1) re = mul(re, x);
x = mul(x, x); y >>= 1;
}
return re;
}
int C(int n, int m) {
if (n < 0 || n < m || m < 0) return 0;
return mul(mul(jc[n], invs[m]), invs[n - m]);
}
int main() {
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
jc[0] = 1; for (int i = 1; i < N; i++) jc[i] = mul(jc[i - 1], i);
inv[0] = inv[1] = 1; for (int i = 2; i < N; i++) inv[i] = mul(inv[mo % i], mo - mo / i);
invs[0] = 1; for (int i = 1; i < N; i++) invs[i] = mul(invs[i - 1], inv[i]);
scanf("%d %d %d", &n, &m, &s);
if (n == 1 && m == 1) {printf("%d", (s == 0) ? 1 : 0); return 0;}
if (n == 1) {printf("%d", C(m - 1 + s - 1, s)); return 0;}
if (m == 1) {printf("%d", C(n - 1 + s - 1, s)); return 0;}
int ans = 0;
for (int a = 0; a < m - 1; a++) {
int b = n - 1, c = m - 1 - a;
ll d = 1ll * n * m - 1 - 2 * (a + b) - c;
ans = add(ans, mul(ksm(s + 1, d), mul(C(a + b - 1, a), C(s - b + 2 * a + 2 * b + c - 1, 2 * a + 2 * b + c - 1))));
}
for (int b = 0; b < n - 1; b++) {
int a = m - 1, c = n - 1 - b;
ll d = 1ll * n * m - 1 - 2 * (a + b) - c;
ans = add(ans, mul(ksm(s + 1, d), mul(C(a + b - 1, b), C(s - b + 2 * a + 2 * b + c - 1, 2 * a + 2 * b + c - 1))));
}
printf("%d", ans);
return 0;
}