【YBT2023寒假Day5 C】路径计数(数学)(生成函数)

路径计数

题目链接:YBT2023寒假Day5 C

题目大意

有一个 h*w 的网格,你要从左上角走到右下角,每次可以向右或者向下,定义一条路径的分数是它经过的位置的权值和。
每次会选择权值大的点走过去,如果权值相同,就走右边,如果只有一个方向可以走就走那个方向。
一直左上角权值为 0,其它格子值域 0~S,问你有多少个网格满足路径的分数恰好是 S。

思路

考虑到网格中有很多的地方是没有限制的,而这个数量你会发现并不是固定的。
考虑到移动可以因此分为几类。
(没有限制的向右走,没有限制的向下走,有限制的往限制的方向走)

不过会注意到其实最后会有限制,但两种限制其实不同,考虑分别求贡献加起来。
假设接触了下边界,碰的位置是 ( h , a + 1 ) (h,a+1) (h,a+1),也就是已经往右走了 a a a 次。
那往下走的次数 b = h − 1 b=h-1 b=h1,最后限制的走要走 c = w − 1 − a c=w-1-a c=w1a 次。

考虑到没有限制的操作会固定一个格子的值,限制一个格子的取值,而有限制的操作会固定一个格子的值。
那任意填数的格子数 d = h w − 1 − 2 a − 2 b − c d=hw-1-2a-2b-c d=hw12a2bc

然后考虑每种操作分开来 DP 或者什么的,就比如无限制往右走,那就是 f i , j f_{i,j} fi,j 为进行 i i i 次这个操作,权值和是 j j j 的方案数。
(这里就是把 j j j 拆成 i i i 个非负整数的所有方案,这 i i i 个数各自加一的乘积之和)
(加一是因为相等的时候往右走,可以相等)

那无限制往右走就是 g i , j g_{i,j} gi,j 为把 j j j 拆成 i i i 个非负整数的所有方案,这些数的乘积之和。
而有限制的,就是 h i , j h_{i,j} hi,j 为把 j j j 拆成 i i i 个非负整数的方案数。

那总答案就是:(注意组合数是 ( a + b − 1 a ) \binom{a+b-1}{a} (aa+b1) 是因为最后一步一定是 b b b
( S + 1 ) d ( a + b − 1 a ) ∑ x + y ⩽ S f a , x g b , y h c , S − x − y (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} (S+1)d(aa+b1)x+ySfa,xgb,yhc,Sxy

但是这还不够,考虑后面枚举的部分要怎么处理。
考虑到这个 DP 都很简单,考虑用看看转移式:
f i , j = ∑ k = 0 j f i − 1 , j − k ( k + 1 ) f_{i,j}=\sum\limits_{k=0}^jf_{i-1,j-k}(k+1) fi,j=k=0jfi1,jk(k+1)
g i , j = ∑ k = 0 j f i − 1 , j − k k g_{i,j}=\sum\limits_{k=0}^{j}f_{i-1,j-k}k gi,j=k=0jfi1,jkk
h i , j = ∑ k = 0 j f i − 1 , j − k h_{i,j}=\sum\limits_{k=0}^jf_{i-1,j-k} hi,j=k=0jfi1,jk
发现是卷积的形式。

然后我们尝试生成函数:
F ( x ) = ∑ i ⩾ 0 ( i + 1 ) x i F(x)=\sum\limits_{i\geqslant 0}(i+1)x^i F(x)=i0(i+1)xi
G ( x ) = ∑ i ⩾ 0 i x i G(x)=\sum\limits_{i\geqslant 0}ix^i G(x)=i0ixi
H ( x ) = ∑ i ⩾ 0 x i H(x)=\sum\limits_{i\geqslant 0}x^i H(x)=i0xi
然后弄封闭形式:(错位相减,就只写 F ( x ) F(x) F(x) 的例子了)

x F ( x ) = ∑ i ⩾ 0 ( i + 1 ) x i + 1 = ∑ i ⩾ 1 i x i xF(x)=\sum\limits_{i\geqslant 0}(i+1)x^{i+1}=\sum\limits_{i\geqslant 1}ix^i xF(x)=i0(i+1)xi+1=i1ixi
F ( x ) − x F ( x ) = 1 + ∑ i ⩾ 1 x i = ∑ i ⩾ 0 x i F(x)-xF(x)=1+\sum\limits_{i\geqslant 1}x^i=\sum\limits_{i\geqslant 0}x^i F(x)xF(x)=1+i1xi=i0xi
F ( x ) = ∑ i ⩾ 0 x i 1 − x F(x)=\dfrac{\sum\limits_{i\geqslant 0}x^i}{1-x} F(x)=1xi0xi

然后这个是 H ( x ) H(x) H(x),然后同样的道理(或者这个经典的形式),就得到 H ( x ) = 1 1 − x H(x)=\dfrac{1}{1-x} H(x)=1x1
F ( x ) = 1 ( 1 − x ) 2 F(x)=\dfrac{1}{(1-x)^2} F(x)=(1x)21 G ( x ) G(x) G(x) 同理有 G ( x ) = x ( 1 − x ) 2 G(x)=\dfrac{x}{(1-x)^2} G(x)=(1x)2x

然后放进后面枚举的式子里面:
∑ x + y ⩽ S f a , x g b , y h c , S − x − y \sum\limits_{x+y\leqslant S}f_{a,x}g_{b,y}h_{c,S-x-y} x+ySfa,xgb,yhc,Sxy
其实就是要:
[ x S ] ( F a G b H c ) ( x ) = [ x S ] x b ( 1 − x ) 2 a + 2 b + c = [ x S − b ] 1 ( 1 − x ) 2 a + 2 b + c [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}} [xS](FaGbHc)(x)=[xS](1x)2a+2b+cxb=[xSb](1x)2a+2b+c1

不过 1 1 − x \dfrac{1}{1-x} 1x1 ∑ i ⩾ 0 x i \sum\limits_{i\geqslant 0}x^i i0xi 的封闭形式,那这个就是 2 a + 2 b + c 2a+2b+c 2a+2b+c 个卷积起来。
那在组合意义上,它的 x S − b x^{S-b} xSb 项,就是用 2 a + 2 b + c 2a+2b+c 2a+2b+c 个非负整数组成 S − b S-b Sb 的方案数。
那这个我们用插板法,就是 ( S − b + 2 a + 2 b + c − 1 2 a + 2 b + c − 1 ) \binom{S-b+2a+2b+c-1}{2a+2b+c-1} (2a+2b+c1Sb+2a+2b+c1)

代码

#include
#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;
}

你可能感兴趣的:(#,数学或数论,#,生成函数,数学,生成函数)