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

路径计数

题目链接:YBT2023寒假Day5 C

题目大意

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

思路

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

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

考虑到没有限制的操作会固定一个格子的值,限制一个格子的取值,而有限制的操作会固定一个格子的值。
那任意填数的格子数 d=hw12a2bc

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

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

那总答案就是:(注意组合数是 (a+b1a) 是因为最后一步一定是 b
(S+1)d(a+b1a)x+ySfa,xgb,yhc,Sxy

但是这还不够,考虑后面枚举的部分要怎么处理。
考虑到这个 DP 都很简单,考虑用看看转移式:
fi,j=k=0jfi1,jk(k+1)
gi,j=k=0jfi1,jkk
hi,j=k=0jfi1,jk
发现是卷积的形式。

然后我们尝试生成函数:
F(x)=i0(i+1)xi
G(x)=i0ixi
H(x)=i0xi
然后弄封闭形式:(错位相减,就只写 F(x) 的例子了)

xF(x)=i0(i+1)xi+1=i1ixi
F(x)xF(x)=1+i1xi=i0xi
F(x)=i0xi1x

然后这个是 H(x),然后同样的道理(或者这个经典的形式),就得到 H(x)=11x
F(x)=1(1x)2G(x) 同理有 G(x)=x(1x)2

然后放进后面枚举的式子里面:
x+ySfa,xgb,yhc,Sxy
其实就是要:
[xS](FaGbHc)(x)=[xS]xb(1x)2a+2b+c=[xSb]1(1x)2a+2b+c

不过 11xi0xi 的封闭形式,那这个就是 2a+2b+c 个卷积起来。
那在组合意义上,它的 xSb 项,就是用 2a+2b+c 个非负整数组成 Sb 的方案数。
那这个我们用插板法,就是 (Sb+2a+2b+c12a+2b+c1)

代码

#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;
}
posted @   あおいSakura  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示