《洛谷题目CF708E Student‘s Camp 学生营地》 高端局题解!!!

目录

题目描述

输入描述

输出描述

解析

完整代码


 

来,在这之前,先问大家一句,你们几年级了,打在评论区里;

至于我为什么要问这个问题,是因为下面的文章会惊艳到你^_^

下面有投票和代码!!!



目录

题目描述

输入描述

输出描述

解析

完整代码

温馨提示:这把高端局!!!:

输入格式

输出格式

题意翻译

输入输出样例

说明/提示


首先转化题意。题目中已经限定了最高和最低一行的方块不会被吹走,所以整个建筑「不连通」只可能是被「上下劈开」,不可能是被「左右劈开」。也就是说,整个建筑「连通」,当且仅当对于所有相邻的两行,它们最后剩下的方块连通。

考虑 DP。设 �(�,�,�)f(i,l,r) 表示第 �i 行剩下 [�,�][l,r] 的方块且建筑不倒的概率。

先考虑某一行剩下 [�,�][l,r] 的方块的概率。

设 �(�)D(i) 表示 �k 次「吹风」中 �i 次成功的概率,显然

�(�)=(��)��(1−�)�−�D(i)=(ik​)pi(1−p)k−i

可以 �(�)O(k) 预处理求出所有的 �(�)D(i)。

设 �(�,�) (1≤�≤�≤�)P(l,r) (1≤l≤r≤m) 表示某一行只剩下 [�,�][l,r] 的砖块的概率,由于左右两边相互独立,可以得到

�(�,�)=�(�−1)�(�−�)P(l,r)=D(l−1)D(m−r)

接下来考虑如何从第 �−1i−1 行转移。

由前面的分析,第 �i 行最后剩下的方块所占的区间要和第 �−1i−1 行的区间有交,即

�(�,�,�)=�(�,�)∑[�′,�′]∩[�,�]≠∅�(�−1,�′,�′)f(i,l,r)=P(l,r)[l′,r′]∩[l,r]=∅∑​f(i−1,l′,r′)

边界是 �(0,1,�)=1f(0,1,m)=1,答案即为所有 �(�,�,�)f(n,l,r) 的和。

这样状态数为 �(��2)O(nm2),转移复杂度 �(�2)O(m2),需要优化。

考虑问题的反面,即 [�,�][l,r] 和 [�′,�′][l′,r′] 没有交集。容易发现,这只有两种互斥的情况:要么 �′<�r′<l,要么 �′>�l′>r。

于是,我们得到:

�(�,�,�)=�(�,�)[∑�′≤�′�(�−1,�′,�′)−∑�′<��(�−1,�′,�′)−∑�′>��(�−1,�′,�′)]f(i,l,r)=P(l,r)[l′≤r′∑​f(i−1,l′,r′)−r′<l∑​f(i−1,l′,r′)−l′>r∑​f(i−1,l′,r′)]

发现只要求出三个求和号的部分就能转移了,即

�(�)=∑�≤��(�,�,�)�(�,�)=∑�≤�<��(�,�,�)�(�,�)=∑�≥�>��(�,�,�)�(�,�,�)=�(�,�)[�(�−1)−�(�−1,�)−�(�−1,�)]F(i)L(i,x)R(i,x)f(i,l,r)​=l≤r∑​f(i,l,r)=l≤r<x∑​f(i,l,r)=r≥l>x∑​f(i,l,r)=P(l,r)[F(i−1)−L(i−1,l)−R(i−1,r)]​

注意其中 �(�)F(i) 的意义,它表示前 �i 行连通的概率,那么答案就是 �(�)F(n)。

细心的同学可能已经发现了,�(�,�)L(i,x) 和 �(�,�)R(i,x) 在转移、形式和结果上都是高度对称的。事实上,把 �(�,�)L(i,x) 左右翻转就可以得到 �(�,�)R(i,x),即 �(�,�)=�(�,�−�+1)L(i,x)=R(i,m−x+1)。于是我们只要讨论 �(�,�)L(i,x) 的处理就可以了。

由于 �(�,�,�)f(i,l,r) 会对所有 �>�x>r 的 �(�,�)L(i,x) 产生贡献,我们可以先记 ��(�,�)SL​(i,r) 表示所有右端点为 �r 的 �(�,�,�)f(i,l,r) 之和,则对 ��SL​ 计算前缀和即可得到 �L,用公式表达就是:

��(�,�)=∑�≤��(�,�,�)�(�,�)=∑�<���(�,�)SL​(i,r)L(i,x)​=l≤r∑​f(i,l,r)=r<x∑​SL​(i,r)​

同时我们可以发现,所有的 ��(�,�)SL​(i,r) 之和就等于 �(�)F(i),这样就把 �(�)F(i) 也一并解决了。

接下来考虑如何计算 ��(�,�)SL​(i,r),把 �(�,�,�)f(i,l,r) 的转移式代入:

��(�,�)=∑�≤��(�,�,�)=∑�≤��(�,�)[�(�−1)−�(�−1,�)−�(�−1,�)]=�(�−�)∑�≤��(�−1)[�(�−1)−�(�−1,�)−�(�−1,�)]=�(�−�)([�(�−1)−�(�−1,�)]∑�≤��(�−1)−∑�≤��(�−1)�(�−1,�))SL​(i,r)​=l≤r∑​f(i,l,r)=l≤r∑​P(l,r)[F(i−1)−L(i−1,l)−R(i−1,r)]=D(m−r)l≤r∑​D(l−1)[F(i−1)−L(i−1,l)−R(i−1,r)]=D(m−r)([F(i−1)−R(i−1,r)]l≤r∑​D(l−1)−l≤r∑​D(l−1)L(i−1,l))​

到这里思路已经很清晰了,对 �(�−1)D(l−1) 和 �(�−1)�(�−1,�)D(l−1)L(i−1,l) 做前缀和即可实现 �(1)O(1) 转移,最后答案即为 �(�)F(n)。边界同样是 �(0,1,�)=1f(0,1,m)=1,即 ��(0,�)=1SL​(0,m)=1。

总时间复杂度 �(��+�)O(nm+k),空间复杂度可以用滚动数组优化至 �(�+�)O(m+k)。

我们把行编号为 0∼�+10∼n+1,对于一个连通的状态,必然存在一条从第 00 行到第 �+1n+1 行的只往左、右、下三个方向走的路径,途中只经过未被摧毁的方块。为了让每个连通的状态对应唯一一条路径,我们强制这条路径:

  • 能往下走时,直接往下走;
  • 否则,往左或往右走到最近的能往下走的方块,然后往下走。

考虑 DP,设 ��,�fi,j​ 表示从第 �−1i−1 行进入第 �i 行时在第 �j 列,且在第 �−1i−1 行有往右走时,第 1∼�−11∼i−1 行的摧毁状态对应的概率;��,�gi,j​ 的定义是将上面的 “往右走” 改成 “往左走”;ℎ�,�hi,j​ 的定义是将 “往右走” 改成 “没有左右移动”。

��fi​ 由 ��−1,ℎ�−1fi−1​,hi−1​ 转移而来,��gi​ 由 ��−1,ℎ�−1gi−1​,hi−1​ 转移而来,ℎ�hi​ 由 ��−1,��−1,ℎ�−1fi−1​,gi−1​,hi−1​ 转移而来。计算第 �i 行的 DP 值时,我们算上第 �−1i−1 行的摧毁状态对应的概率。转移式请读者自行推导(雾)。

转移可以前缀和优化,时间复杂度 �(��)O(nm)。

根据这种 “路径” 的思想,应该有更简单的 DP 方法,请读者自行思考。具体地,我们可以调整 “强制每个连通的状态对应唯一一条路径” 的方式。我只是遵循刚想到这种思路时得到的 DP 方法罢了。

很明显,本题是一道概率论。因此,我们必须做好愉快推柿子的准备 qwq。

由于恶心的状态设置与求逆元的繁琐工作(对于本蒟蒻而言),作者花了不少时间才弄明白,便写篇题解加深印象。

为了帮助和本蒟蒻一样蒻的人,本篇题解会详解上述两个部分。

解题思路

题目让我们求一个 (�+2)×�(n+2)×m 的网格 �k 天后保持联通的概率。除了首行和末行,其他 �n 行左右端点的格子均有 �=��p=ba​ 的概率消失。为方便阐述与理解,将中间 �n 行编号为 1∼�1∼n,首行和末行编号为 00 和 �+1n+1。

首先针对其中一行进行分析。容易得到,其中一行在第 �k 天恰好消失 �i 个的概率为:

(��)×��×(1−�)�−�(ki​)×pi×(1−p)k−i

也就是说,�i 天有格子消失,�−�k−i 天没有格子消失,并在 �k 天内选出有格子 消失的 �i 天。

理清楚了一行的状态,接下来考虑转移。由于图的对称性,我们可以尝试置状态 ���,�dpi,r​ 表示第 �i 行右端点为 �r,且 00 至 �i 行联通的概率,而舍弃左端点。为什么可以这样设置呢?我们在转移时分情况讨论。

在转移的过程中,我们枚举左端点。因为丢失了左端点信息,所以判断相交较为复杂。而正难则反,我们考虑求不相交的方案,并从总方案中减去,这样在转移过程中只需要判断 �′<�r′<l 或 �′<�l′<r。

  1. 对于 �′<�r′<l,只需在枚举 �l 时直接计算 ∑�=1�−1���−1,�∑j=1l−1​dpi−1,j​ 即可。
  2. 对于 �′>�l′>r,此时利用对称性,���,�dpi,r​ 亦可表示左端点为 �−�+1m−r+1 且联通的概率,即左端点 �′l′ 可以用右端点 �−�′+1m−l′+1 来表示,那么计算 ∑�=1�−����−1,�∑j=1m−r​dpi−1,j​ 即可。

所以得到转移:

���,�=∑�=1���−1��−�(∑�=1����−1,�−∑�=1�−1���−1,�−∑�=1�−����−1,�)dpi,r​=l=1∑r​pl−1​pm−r​(j=1∑m​dpi−1,j​−j=1∑l−1​dpi−1,j​−j=1∑m−r​dpi−1,j​)

这样转移的复杂度为 �(��3)O(nm3)。为了加速求和,设 ���−1,�dpi−1,j​ 的前缀和为 _���_dpj​,则:

���,�=∑�=1���−1��−�(_���−_���−1−_���−�)dpi,r​=l=1∑r​pl−1​pm−r​(_dpm​−_dpl−1​−_dpm−r​)=��−�((_���−_���−�)∑�=1���−1−∑�=1�_���−1��−1)=pm−r​((_dpm​−_dpm−r​)l=1∑r​pl−1​−l=1∑r​_dpl−1​pl−1​)

数学好的巨佬可以跳过此段。为什么要这样拆项呢?容易观察得到:��−�,_���,_���−�pm−r​,_dpm​,_dpm−r​ 是与变量 �l 无关的项,��−1pl−1​ 是与 �p 和变量 �l 有关的项,而 _���−1��−1_dpl−1​pl−1​ 是与 _��,�_dp,p 以及变量 �l 有关的项。首先将和变量有无关系的项区分开,再对与变量有关的项根据其次数进行分类,这样往往能对一个整式进行高效的处理。

接着,我们再设 ��pl​ 的前缀为 _��_pl​,_�����_dpl​pl​ 的前缀为 ����dppl​ 则:

���,�=��−�((_���−_���−�)_��−1−����−1)dpi,r​=pm−r​((_dpm​−_dpm−r​)_pr−1​−dppr−1​)

通过前缀和优化,我们把复杂降到了 �(��)O(nm)。

代码实现

代码实现较为简单,但本蒟蒻仍认为实现的难点在于逆元,因此代码中求逆元的部分会详细注释。

Code

#include <iostream>
using namespace std;
typedef long long ll;
const ll N = 1.5e3 + 10, M = 1e5 + 10, mod = 1e9 + 7;
ll n, m, a, b, k, p1, p2, ans, dp[N][N], _p[N], p[N], dpp[N], _dp[N], fac[M], inv[M]; //变量定义如题、如上文所述,其中 fac 为阶乘,inv 为逆元。
ll pow(ll x, ll y) { ll res = 1; while (y) { if (y & 1) res = res * x % mod; x = x * x % mod, y >>= 1; } return res; } //快速幂。
ll C(ll n, ll m) { return fac[n] * inv[n - m] % mod * inv[m] % mod; } //组合数。
signed main() {
	scanf("%lld%lld%lld%lld%lld", &n, &m, &a, &b, &k);
	p1 = a * pow(b, mod - 2) % mod, p2 = (1 - p1 + mod) % mod; //p1 为格子消失概率,p2 为格子不消失的概率,通过费马小定理求得。
	fac[0] = fac[1] = 1;
	for (ll i = 2; i <= k; ++ i) fac[i] = fac[i - 1] * i % mod; //预处理阶乘。
	inv[k] = pow(fac[k], mod - 2); //同样通过费马小定理求得逆元末项。
	for (ll i = k - 1; i >= 0; -- i) inv[i] = inv[i + 1] * (i + 1) % mod; //逆推,1 / (i !) = 1 / ((i + 1) !) * (i + 1)。
	for (ll i = 0; i <= k && i <= m; ++ i) p[i] = C(k, i) * pow(p1, i) % mod * pow(p2, k - i) % mod; // 预处理 k 天消失 i 个格子的概率。
	dp[0][m] = 1, _p[0] = p[0];
	for (ll i = 1; i <= m; ++ i) _p[i] = (_p[i - 1] + p[i]) % mod; //p 的前缀。
	for (ll i = 1; i <= n; ++ i) {
		for (ll j = 1; j <= m; ++ j) _dp[j] = _dp[j - 1] + dp[i - 1][j], _dp[j] %= mod, dpp[j] = (dpp[j - 1] + p[j] * _dp[j] % mod) % mod;
		for (ll j = 1; j <= m; ++ j) dp[i][j] = p[m - j] * ((_dp[m] - _dp[m - j] + mod) % mod * _p[j - 1] % mod - dpp[j - 1] + mod) % mod;
	}//如上文所述。
	for (ll i = 1; i <= m; ++ i) ans = (ans + dp[n][i]) % mod; //累计答案。
	printf("%lld\n", ans);
	return 0;
}

和现有题解都不太一样的魔怔做法。

直接 dp 是 �(��2)O(nm2),很多人都考虑直接前缀和优化状态,这里采用另一种方法。

看到网格连通块想到网格容斥。

直接网格容斥是错的,因为可能出现凹槽。

对于一种合法方案,那其实是要求每相邻两行都有重合的部分。

对相邻两行重合的部分上网格容斥,假设重合部分是 �S,统计 ∑�[�∈�]−∑�[�,�+1∈�]∑i​[i∈S]−∑i​[i,i+1∈S]。

最终每个地方都合法的概率相当于求的是上面的式子的乘积的期望。设 ��Si​ 表示最终每一行剩下的位置构成的集合,我们求的东西就是 ∏�=1�−1(∑�=1�[�∈��∩��+1]−∑�=1�−1[�,�+1∈��∩��+1])∏i=1n−1​(∑j=1m​[j∈Si​∩Si+1​]−∑j=1m−1​[j,j+1∈Si​∩Si+1​]) 这个式子的期望。

相当于没两行相邻指定一个 �i 必选的贡献是 11,指定 �,�+1i,i+1 必选的贡献是 −1−1。

然后对着这个东西 dp,设 ��,�,0/1fi,j,0/1​ 表示前 �i 行,第 �i 行指定了 �j 或者 �,�+1j,j+1 的情况的期望。

转移枚举下一行钦定的种类和位置 �/�,�+1k/k,k+1,这个复杂度时 �(��2)O(nm2),和暴力 dp 一样。

但是容易发现一行的概率只和钦定位置的最左侧和最右侧有关,可以分别讨论当前钦定位置在上一个钦定位置的左侧还是右侧,直接前缀和优化即可。复杂度 �(��+�)O(nm+k) 或者 �(��+�log⁡�)O(nm+klogp)。

#include<bits/stdc++.h>
using namespace std;
const int N=1510,M=1e5+10,p=1e9+7;
int n,m,k,a,b;
int ifac[M],fac[M],s[M];
int qpow(int x,int y=p-2){
	int m=1;
	for(;y;y>>=1,x=1ll*x*x%p)if(y&1)m=1ll*m*x%p;
	return m;
}
int f[N][N][2],sf[N],sg[N];
int C(int n,int m){
	return 1ll*fac[n]*ifac[m]%p*ifac[n-m]%p;
}
signed main(){
	scanf("%d%d%d%d%d",&n,&m,&a,&b,&k);
	fac[0]=1;
	for(int i=1;i<=k;i++)fac[i]=1ll*fac[i-1]*i%p;
	ifac[k]=qpow(fac[k]);
	for(int i=k-1;i>=0;i--)ifac[i]=1ll*ifac[i+1]*(i+1)%p;
	int pl=1ll*a*qpow(b)%p;
	s[0]=qpow(p+1-pl,k);
	for(int i=1;i<=m;i++){
		if(i<=k)s[i]=(s[i-1]+1ll*C(k,i)*qpow(pl,i)%p*qpow(p+1-pl,k-i))%p;
		else s[i]=s[i-1];
	}
	for(int j=1;j<=m;j++){
		f[1][j][0]=1ll*s[j-1]*s[m-j]%p;
		if(j<m)f[1][j][1]=(p-1ll*s[j-1]*s[m-j-1]%p)%p;
	}
	for(int i=2;i<=n;i++){
		for(int j=1;j<=m;j++){
			sf[j]=(sf[j-1]+1ll*f[i-1][j][0]*s[j-1])%p;
			if(j>1)sf[j]=(sf[j]+1ll*f[i-1][j-1][1]*s[j-2])%p;
		}
		for(int j=m;j>=1;j--){
			sg[j]=(sg[j+1]+1ll*f[i-1][j][0]*s[m-j])%p;
			if(j<m)sg[j]=(sg[j]+1ll*f[i-1][j][1]*s[m-j-1])%p;
		}
		for(int j=1;j<=m;j++){
			f[i][j][0]=(1ll*sf[j]*s[m-j]%p+1ll*sg[j]*s[j-1]%p)%p;
			f[i][j][0]=(f[i][j][0]-1ll*f[i-1][j][0]*s[j-1]%p*s[m-j]%p+p)%p;
		}
		for(int j=1;j<m;j++){
			f[i][j][1]=(1ll*sf[j]*s[m-j-1]%p+1ll*sg[j+1]*s[j-1]%p)%p;
			f[i][j][1]=(f[i][j][1]+1ll*f[i-1][j][1]*s[j-1]%p*s[m-j-1]%p+p)%p;
			f[i][j][1]=(p-f[i][j][1])%p;
		}
	}
	int ans=0;
	for(int j=1;j<=m;j++)ans=(ans+f[n][j][0])%p;
	for(int j=1;j<m;j++)ans=(ans+f[n][j][1])%p;
	printf("%d\n",ans);
}

提供一种新的解法。

分析题目:连通意味着从第一行能够走到最后一行,也就意味着任意相邻两行直接都有至少一个公共位置。

左右两边的消失是独立的,我们设 ℎ�hx​ 为 �k 天内共消失 �x 个格子的概率,��=∑�=0�ℎ�sx​=∑i=0x​hx​。ℎ�hx​ 很好求,它等于:

(��)��(1−�)�−�(xk​)px(1−p)k−x

考虑转化「任意相邻两行都有至少一个公共位置」这一条件。设第一行左右两边共消失了 �1,�1l1​,r1​ 个,第二行消失了 �2,�2l2​,r2​ 个格子。根据条件,存在 1≤�≤�1≤x≤m 使得 �1,�2<�≤�−�1,�−�2l1​,l2​<x≤m−r1​,m−r2​。继续转化一下:

{�1<�−�1�1<�−�2�2<�−�1�2<�−�2⎩⎨⎧​l1​<m−r1​l1​<m−r2​l2​<m−r1​l2​<m−r2​​

也就是说,

{�1+�1<��2+�2<��1+�2<��2+�1<�⎩⎨⎧​l1​+r1​<ml2​+r2​<ml1​+r2​<ml2​+r1​<m​

不难发现这个条件是充要的。

根据不等式,如果我们钦定了 �1⋯�l1⋯n​,那么 �1⋯�r1⋯n​ 的最大值也就随之可以求出,答案也就可以容易得到。

我们发现,�1r1​ 受 �1,�2l1​,l2​ 限制,�2r2​ 受 �1,�2,�3l1​,l2​,l3​ 限制,�3r3​ 受 �2,�3,�4l2​,l3​,l4​ 限制,⋯⋯⋯⋯,��rn​ 受 ��−1,��ln−1​,ln​ 限制,且限制的内容仅与它们的最大值有关。

根据限制可以得到启发:设 ��,�,0/1fi,j,0/1​ 表示考虑到第 �i 个位置,max⁡(��−1,��)=�max(li−1​,li​)=j,且(下标最小的)最大值在 �−1/�i−1/i 处的概率之和。

这个动态规划的关键在于 ��−1,�,�fi−1,a,b​ 到 ��,�,�fi,c,d​ 之间的转移。在转移过程中,我们需要考虑 ��−1ri−1​ 和 ��−1li−1​ 的值。

  • 对于 ��−1ri−1​,很好处理,它的贡献就是 ��−1−max⁡(�,�)sm−1−max(a,c)​。
  • 对于 ��−1li−1​,分类讨论:
    1. �=0,�=0b=0,d=0:此时要求 �≤�c≤a,贡献为 ℎ�hc​。
    2. �=0,�=1b=0,d=1:此时要求 �≥1c≥1,贡献为 �min⁡(�,�−1)smin(a,c−1)​。
    3. �=1,�=0b=1,d=0:此时要求 �=�a=c,贡献为 ℎ�ha​。
    4. �=1,�=1b=1,d=1:此时要求 �<�a<c,贡献为 ℎ�ha​。

注意特殊处理前面两个位置和最后两个位置。

这样做是 �(��2+�)O(nm2+k) 的,过不去。

但是这个东西可以前缀和优化,最终时间复杂度 �(��+�)O(nm+k),可以顺利通过。

题目简述

有一个 (�+2)×�(n+2)×m 的网格。除了第一行和最后一行,其他每一行每一天都有 �p 的概率消失。求 �k 天后网格始终保持连通的概率。答案对 109+7109+7 取模。

数据范围 �,�≤1.5×103,�≤105n,m≤1.5×103,k≤105。

分析

相信读者通过读题已经知道了这道题是动态规划+优化排列组合的知识本文就不细讲,不懂的可以点开链接去学一下。

为了方便分析,下文称第一行和最后一行分别为 00 和 �+1n+1 行。首先,我们很容易就能想到状态 ��,�,�fi,l,r​ 表示第 �i 行,区间为 [�,�][l,r],第 00 行到第 �i 行都连同的概率。一行区间为 [�,�][l,r] 的概率,为左边消失恰好 �−1l−1 个和右边恰好消失 �−�m−r 个的概率。通过枚举那几天有格子消失,可以得到 �k 天恰好消失 �i 个的概率 ��=(��)��(1−�)�−�gi​=(ik​)pi​(1−p)k−i。即可得到转移:

��,�,�=��−1��−�∑max⁡(�,�′)≤min⁡(�,�′)��−1,�′,�′fi,l,r​=gl−1​gm−r​max(l,l′)≤min(r,r′)∑​fi−1,l′,r′​

减小状态

但是对于这样的状态定义无论怎么优化,状态数是 �(��2)O(nm2) 的,显然是过大了。我们要考虑减小状态,只保留区间长度是无法继续完成转移的,所以一定要保留一个端点。将 ��,�fi,r​ 定义为第 �i 行的右端点 �r,第 00 行到第 �i 行都连通的概率。在转移的时候,枚举左端点 �l,并计算与之相交的区间和。由于这样的定义状态丢失了左端点信息,所以考虑求不相交的方案,并将其从总方案中减去,因为求区间不相交仅仅需要 �′<�r′<l 或 �′<�l′<r。

对于 �′<�r′<l,在枚举 �l 的时候,直接计算 ��−1,�(�≤�−1)fi−1,j​(j≤l−1) 的和即可。

而对于 �′<�l′<r,由于网格是对称的,所以 ��,�fi,r​ 同样可以表示左端点为 �−�+1m−r+1 连通的概率,直接计算 ��−1,�(�≤�−�)fi−1,j​(j≤m−r) 的和即可。

所以就很容易得到转移:

��,�=∑�=1���−1��−�(∑�=1���−1,�−∑�=1�−1��−1,�−∑�=1�−���−1,�)fi,r​=l=1∑r​pl−1​pm−r​(j=1∑m​fi−1,j​−j=1∑l−1​fi−1,j​−j=1∑m−r​fi−1,j​)

前缀和优化

如果不做任何优化,这样转移的时间复杂度是 �(��3)O(nm3) 的,所以仍需要加上一定的优化。为了加速求和,我们可以尝试加入前缀和优化,将 ��−1,�fi−1,j​ 的前缀和记为 ℎ�hj​,那么有:

��,�=∑�=1���−1��−�(ℎ�−ℎ�−1−ℎ�−�)=��−�((ℎ�−ℎ�−�)∑�=1���−1−∑�=1���−1ℎ�−1)fi,r​​=l=1∑r​pl−1​pm−r​(hm​−hl−1​−hm−r​)=pm−r​((hm​−hm−r​)l=1∑r​pl−1​−l=1∑r​pl−1​hl−1​)​

将 ��pi​ 前缀和为 ��ai​,��ℎ�pi​hi​ 前缀和为 ��bi​,则最终的状态转移方程就显而易见了:

��,�=��−�((ℎ�−ℎ�−�)��−1−��−1)fi,r​=pm−r​((hm​−hm−r​)ar−1​−br−1​)

这样就通过前缀和优化将时间复杂度降到了 �(��)O(nm),可以��������Accepted了。

代码

最后给出代码,仅供参考!

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1.5e3+10;
const int MAXS=1e5+10;
const int mod=1e9+7;
typedef long long LL;
void reduce(int &x) {
	x += x >> 31 & mod;
}
int mul(int a,int b) {
	return (LL)a*b%mod;
}
int pow(int a,int b,int res=1) {
	for (;b;b>>=1,a=mul(a,a))
		if (b & 1)
			res=mul(res,a);
	return res;
}
void fma(int &x,int y,int z) {
	x = ((LL)y*z+x)%mod;
}
int n,m,K;
int ps[MAXN],pre[MAXN];
int fac[MAXS],inv[MAXS];
int C(int a,int b) {
	return (LL) fac[a] * inv[b] % mod * inv[a-b] % mod;
}
int f[MAXN][MAXN];
int main() {
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for (int i=2;i<MAXS;++i) {
		fac[i]=mul(fac[i-1],i);
		inv[i]=mul(inv[mod%i],mod-mod/i);
	}
	for (int i=2;i<MAXS;++i)
		inv[i]=mul(inv[i-1],inv[i]);
	scanf("%d%d",&n,&m);
	int a,b;
	scanf("%d%d",&a,&b);
	int P=pow(b,mod-2,a);
	scanf("%d",&K);
	for (int i=0;i<=m && i<=K;++i)
		ps[i]=pow(P,i,pow(1+mod-P,K-i,C(K,i)));
	for (int i=0;i<=m;++i)
		reduce(pre[i]=ps[i]+(i ? pre[i-1] - mod : 0));
	f[0][m]=1;
	for (int i=1;i<=n;++i) {
		static int tp[MAXN],ts[MAXN];
		for (int j=1;j<=m;++j) {//处理前缀和 
			reduce(tp[j]=tp[j-1]+f[i-1][j]-mod);
			fma(ts[j]=ts[j-1],ps[j],tp[j]);
		}
		for (int r=1;r<=m;++r) {//处理转移 
			int sm=mul(pre[r-1],tp[m]-tp[m-r]+mod);
			reduce(sm-=ts[r-1]);
			f[i][r]=mul(ps[m-r],sm);
		}
	}
	int ans=0;
	for (int i=1;i<=m;++i)
		reduce(ans+=f[n][i]-mod);
	printf("%d\n",ans);
	return 0;
}

Part 1:

可以发现,每一行都是独立的,所以我们可以先算出一行 �k 天之后恰好只剩下区间 [�,�][l,r] 的概率。容易设 ��,�,�pk,l,r​ 进行区间 dpdp,但这是 �(�2�)O(m2k) 的。我们又发现其实 左右端点也是独立的,所以可以省掉一维,但 �(��)O(mk) 还是没法做。我们思考我们在干什么,其实我们在求 �k 天之内可走可不走最后走了 �l 步的概率,直接组合数搞定 ��=���⋅��⋅(1−�)�−�pl​=Ckl​⋅Pl⋅(1−P)k−l。那么恰好只剩下 [�,�][l,r] 的概率就是 ��−1⋅��−�pl−1​⋅pn−r​。

Part 2Part 2:

我们设 �(�,�,�)f(n,l,r) 表示前 �n 行联通并且最后一行空余的区间为 [�,�][l,r] 的方案数,转移可以考虑上一层总概率 减去 和 [�,�][l,r] 没有交集的概率。容易得出转移方程:

��,�,�=��−1⋅��−�⋅(∑�′≤�′��−1,�′,�′−∑�′≤�′<���−1,�′,�′−∑�<�′≤�′��−1,�′,�′)fn,l,r​=pl−1​⋅pn−r​⋅(l′≤r′∑​fn−1,l′,r′​−l′≤r′<l∑​fn−1,l′,r′​−r<l′≤r′∑​fn−1,l′,r′​)

然而直接转移是 �(��4)O(nm4) 的。我们发现可以用 前缀和优化。于是现在变成了 �(��2)O(nm2) 还是过不了!如果要 �(��)O(nm) 的复杂度,那么我们既要减少状态数,更要减少转移数,两个老大难问题摆在我们面前。

Part 3Part 3:

我们设 ����=∑�′≤�′��,�′,�′totn​=l′≤r′∑​fn,l′,r′​, ��,�=∑�′≤�′≤���,�′,�′Ln,i​=l′≤r′≤i∑​fn,l′,r′​,��,�=∑�≤�′≤�′��,�′,�′Rn,i​=r≤l′≤r′∑​fn,l′,r′​,则现在方程变成了

��,�,�=��−1⋅��−�⋅(����−1−∑�′=1�−1��−1,�′−∑�′=�+1���−1,�′)fn,l,r​=pl−1​⋅pn−r​⋅(totn−1​−l′=1∑l−1​Ln−1,l′​−r′=r+1∑n​Rn−1,r′​)

并且这个 ��,�,�fn,l,r​ 算完之后要贡献给 ��,�,��,�Ln,r​,Rn,l​。不如直接不管这个中间变量 ��,�,�fn,l,r​ 我们直接计算 �,�L,R 状态之间的转移,这里只给出 ��,�Ln,r​ 的情况:

��,�=∑�=1���,�,�=∑�=1���−1⋅��−�⋅(����−1−∑�′=1�−1��−1,�′−∑�′=�+1���−1,�′)Ln,r​=l=1∑r​fn,l,r​=l=1∑r​pl−1​⋅pn−r​⋅(totn−1​−l′=1∑l−1​Ln−1,l′​−r′=r+1∑n​Rn−1,r′​)

我们发现在这个柿子里面 ��−�,����−1pn−r​,totn−1​ 都是 固定 的,并且一重循环的和式 ∑�=1���−1,∑�′=�+1���−1,�′l=1∑r​pl−1​,r′=r+1∑n​Rn−1,r′​ 可以用前缀和优化。剩下一个二重循环的和式 ∑�=1���−1⋅��−�⋅∑�′=1�−1��−1,�′=��−�∑�=1���−1⋅∑�′=1�−1��−1,�′l=1∑r​pl−1​⋅pn−r​⋅l′=1∑l−1​Ln−1,l′​=pn−r​l=1∑r​pl−1​⋅l′=1∑l−1​Ln−1,l′​ 怎么办呢?其实也可以用前缀和优化,因为现在和式里面算的东西已经和 �r 没有关系了,我们从 �r 到 �+1r+1 的时候,这个和式的变化量就为 ��−���−1⋅∑�′=1�−1��−1,�′pn−r​pr−1​⋅l′=1∑r−1​Ln−1,l′​,因此我们每次维护 ��−1,�′Ln−1,l′​ 的前缀和,然后每次让和式加上这个变化量就好了。

��,�Rn,l​ 的转移也是一样的道理,只要再推一遍柿子。

����totn​ 就是全体 ��,�ln,r​ 或者全体 ��,�Rn,l​ 的和。

细节看起来很多,但是只要真正想清楚码量并不大。

时间复杂度 �(��+�)O(nm+k)。

#include <bits/stdc++.h>
using namespace std;
int N,M,P,K;
const int mod=1e9+7;
int fastpow(int a,int b) {
	int ret=1;
	while(b) {
		if(b&1) ret=1ll*ret*a%mod;
		b>>=1; a=1ll*a*a%mod;
	} return ret;
}
const int MAXN=1.5e3+10;
const int MAXK=1e5+10;
int fact[MAXK],invfact[MAXK];
int powp[MAXK],powinvp[MAXK];
void init() {
	fact[0]=1;
	for(int i=1; i<MAXK; i++)
		fact[i]=1ll*fact[i-1]*i%mod;
	invfact[MAXK-1]=fastpow(fact[MAXK-1],mod-2);
	for(int i=MAXK-1; i>0; i--)
		invfact[i-1]=1ll*invfact[i]*i%mod;
	powp[0]=powinvp[0]=1;
	for(int i=1; i<MAXK; i++)
		powp[i]=1ll*powp[i-1]*P%mod;
	powinvp[1]=(1+mod-P)%mod;
	for(int i=2; i<MAXK; i++)
		powinvp[i]=1ll*powinvp[i-1]*powinvp[1]%mod;
}
int C(int a,int b) {
	if(a<b) return 0;
	return 1ll*fact[a]*invfact[b]%mod*invfact[a-b]%mod;
}
int p[MAXN][MAXN],h[MAXN];
int l[MAXN][MAXN],g[MAXN][MAXN],all[MAXN];
int sumprow[MAXN],sumpcol[MAXN];
void solve() {
	for(int i=0; i<=K; i++)
		h[i]=1ll*C(K,i)*powp[i]%mod*powinvp[K-i]%mod;
	for(int i=1; i<=M; i++)
		for(int j=i; j<=M; j++) {
			p[i][j]=1ll*h[i-1]*h[M-j]%mod;
			all[1]=(all[1]+p[i][j])%mod;
			l[1][j]=(l[1][j]+p[i][j])%mod;
			g[1][i]=(g[1][i]+p[i][j])%mod;
			sumprow[i]=(sumprow[i]+p[i][j])%mod;
			sumpcol[j]=(sumpcol[j]+p[i][j])%mod;
		}
	for(int i=2; i<=N; i++) {
		for(int j=1,suml=0,sumhl=0; j<=M; j++) {
			suml=(suml+l[i-1][j-1])%mod;
			sumhl=(sumhl+1ll*h[j-1]*suml%mod)%mod;
			l[i][j]=(mod-1ll*h[M-j]*sumhl%mod)%mod;
			g[i][j]=1ll*sumprow[j]*(all[i-1]+mod-suml)%mod;
		}
		for(int j=M,sumg=0,sumhg=0; j>=1; j--) {
			sumg=(sumg+g[i-1][j+1])%mod;
			sumhg=(sumhg+1ll*h[M-j]*sumg%mod)%mod;
			l[i][j]=(l[i][j]+1ll*sumpcol[j]*(all[i-1]+mod-sumg)%mod)%mod;
			g[i][j]=(g[i][j]+mod-1ll*h[j-1]*sumhg%mod)%mod;
		}
		for(int j=1; j<=M; j++)
			all[i]=(all[i]+l[i][j])%mod;
	}
}
int main() {
	int a,b; scanf("%d%d%d%d%d",&N,&M,&a,&b,&K);
	P=1ll*a*fastpow(b,mod-2)%mod;
	init();
	solve();
	printf("%d",all[N]);
	return 0;
}

题意

  • 有一个 (�+2)×�(n+2)×m 的网格。
  • 除了第一行和最后一行,其他每一行每一天最左边和最右边的格子都有 �p 的概率消失。
  • 求 �k 天后,网格始终保持连通的概率。
  • �,�≤1.5×103n,m≤1.5×103,�≤105k≤105,答案对 109+7109+7 取模。

题解

网格始终保持连通,等价于 �k 天后任意相邻两行剩下的区间有交。

考虑区间 dp,设 ��,�,�fi,l,r​ 表示前 �i 行连通且第 �i 行剩下的区间为 [�,�][l,r] 的概率。

初始条件为 �0,1,�=1f0,1,m​=1,转移:

��,�,�=��,�∑[�′,�′]∩[�,�]≠∅��−1,�′,�′fi,l,r​=pl,r​[l′,r′]∩[l,r]=∅∑​fi−1,l′,r′​

其中 ��,�pl,r​ 表示 �k 天后剩下 [�,�][l,r] 的概率,有 ��,�=��−1×��−�pl,r​=pl−1​×pm−r​,��=(��)��(1−�)�−�pi​=(ik​)pi(1−p)k−i。

直接做是 �(��2)O(nm2) 的,显然无法接受。

设 ���,�=∑�≤���,�,�fri,r​=∑l≤r​fi,l,r​,���,�=∑�≤����,�sri,j​=∑r≤j​fri,r​。

同理设 ���,�=∑�≤���,�,�fli,l​=∑l≤r​fi,l,r​,���,�=∑�≥����,�sli,j​=∑l≥j​fli,l​。

由于左右完全对称,因此 ���,�=���,�+1−�fli,j​=fri,m+1−j​,���,�=���,�+1−�sli,j​=sri,m+1−j​。

则有:

��,�,�=��,�∑[�′,�′]∩[�,�]≠∅��−1,�′,�′=��,�(���−1,�−���−1,�−1−���−1,�+1)=��,�(���−1,�−���−1,�−1−���−1,�−�)fi,l,r​​=pl,r​[l′,r′]∩[l,r]=∅∑​fi−1,l′,r′​=pl,r​(sri−1,m​−sri−1,l−1​−sli−1,r+1​)=pl,r​(sri−1,m​−sri−1,l−1​−sri−1,m−r​)​

于是我们只需要计算 ���,�fri,j​,然后前缀和求出 ���,�sri,j​ 即可。

又有:

���,�=∑�≤���,�,�=∑�≤���,�(���−1,�−���−1,�−1−���−1,�−�)=((���−1,�−���−1,�−�)��−�∑�≤���−1)−(��−�∑�≤���−1���−1,�−1)=��−�((���−1,�−���−1,�−�)∑�≤���−1−∑�≤���−1���−1,�−1)fri,r​​=l≤r∑​fi,l,r​=l≤r∑​pl,r​(sri−1,m​−sri−1,l−1​−sri−1,m−r​)=((sri−1,m​−sri−1,m−r​)pm−r​l≤r∑​pl−1​)−(pm−r​l≤r∑​pl−1​sri−1,l−1​)=pm−r​((sri−1,m​−sri−1,m−r​)l≤r∑​pl−1​−l≤r∑​pl−1​sri−1,l−1​)​

那么我们可以预处理 ��=∑�≤���−1qr​=∑l≤r​pl−1​,��,�=∑�≤���−1���,�−1gi,r​=∑l≤r​pl−1​sri,l−1​,然后 �(��)O(nm) 计算出所有的 ���,�,���,�fri,j​,sri,j​,从而计算出最终的答案,也就是 ���,�srn,m​。

代码

const int N = 1.5e3 + 7;
int n, m, k, a, b;
modint x, p[N], q[N], f[N][N], s[N][N], g[N][N];

inline modint binom(int i, int j) {
	modint a = 1, b = 1;
	for (int k = 1; k <= j; k++) a *= i + 1 - k, b *= k;
	return a / b;
}

int main() {
	rd(n), rd(m), rd(a), rd(b), rd(k), x = (modint)a / b;
	for (int i = 0; i <= min(m, k); i++)
		p[i] = binom(k, i) * (x ^ i) * ((1 - x) ^ (k - i));
	for (int i = 1; i <= m; i++) q[i] = q[i-1] + p[i-1];
	f[0][m] = s[0][m] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			f[i][j] = p[m-j] * ((s[i-1][m] - s[i-1][m-j]) * q[j] - g[i-1][j]),
			s[i][j] = s[i][j-1] + f[i][j],
			g[i][j] = g[i][j-1] + p[j-1] * s[i][j-1];
	print(s[n][m]);
	return 0;
}

由于公式显示在题解界面是出现了一点巨大的偏差,部分挂掉的公式就用了图,当然也可以到博客中看,这样体验更好些,希望大家能谅解。

一个简单的想法就是我们令 ℎ�,�,�hi,l,r​ 表示 �k 天后第 �i 行还剩下 [�,�][l,r],前 �i 行联通的概率。��,�Pl,r​ 表示单独一行只剩 [�,�][l,r] 的概率。

首先我们有:

接下来我们考虑 ��,�Pl,r​,我们令 ��=(��)��(1−�)�−�qi​=(ik​)pi(1−p)k−i,不难发现 ��,�=��−1��−�Pl,r​=ql−1​qm−r​。

然后我们令 ��,�fi,r​ 表示右端点为 �r 的所有 ℎh 之和,同理 ��,�gi,l​ 表示以左端点为 �l 的所有 ℎh 之和,再令 ��,�Fi,r​ 表示右端点小于等于 �r 的所有 ℎh 之和,同理 ��,�Gi,l​ 表示以左端点大于等于 �l 的所有 ℎh 之和。

具体地:

于是我们就有:

将 ℎh 代入 �f 和 �g,我们有:

考虑到最后答案是 ��,�Fn,m​,我们发现我们可以绕过 ℎh 来求 �F。

最终复杂度 �(�+��)O(k+nm)。

 

Codeforces 题目传送门 & 洛谷题目传送门

神仙 *3100,%%%

首先容易注意到 ∀�∈[1,�]∀i∈[1,m],第 �i 行剩余的砖块一定构成一个区间,设其为 [��,��][li​,ri​]。

其次,由于第 00 行和第 �+1m+1 行的砖块不可能被风吹走,因此该建筑物只可能被上下劈开,i.e.,该建筑物被劈开当且仅当 ∃�∈[1,�),[��,��]∩[��+1,��+1]=∅∃i∈[1,m),[li​,ri​]∩[li+1​,ri+1​]=∅。

这时候就可以考虑 ��dp 了,���,�,�dpi,l,r​ 表示考虑到第 �i 行,第 �i 行剩余的砖块组成的区间为 [�,�][l,r] 的概率。因此我们就有了优秀的 �5n5 的 ��dp:���,�,�=�(�,�)×∑[�,�]∩[�′,�′]≠∅���−1,�′,�′dpi,l,r​=P(l,r)×[l,r]∩[l′,r′]=∅∑​dpi−1,l′,r′​,其中 �(�,�)P(l,r) 为某一行被风吹得恰好只剩 [�,�][l,r] 的概率,如果我们记 �(�)f(i) 表示恰好 �i 个砖块被吹走的概率,那么有 �(�)=(��)��(1−�)�−�f(i)=(ik​)pi(1−p)k−i,�(�,�)=�(�−1)�(�−�)P(l,r)=f(l−1)f(m−r)。

显然我们要对这个 ��dp 进行优化。怎么优化呢?考虑问题的反面,可拿概率减去 [�,�]∩[�′,�′]=∅[l,r]∩[l′,r′]=∅ 的概率,显然概率为 ∑1≤�≤�≤����−1,�,�1≤l≤r≤m∑​dpi−1,l,r​,而 [�,�]∩[�′,�′]=∅[l,r]∩[l′,r′]=∅ 当且仅当 �′<�∨�′>�r′<l∨l′>r,故 [�,�]∩[�′,�′]=∅[l,r]∩[l′,r′]=∅ 的概率为 ∑1≤�′≤�′<����−1,�,�+∑�<�′≤�′≤����−1,�,�1≤l′≤r′<l∑​dpi−1,l,r​+r<l′≤r′≤m∑​dpi−1,l,r​,因此 ���,�,�=�(�,�)(∑1≤�≤�≤����−1,�,�−∑1≤�′≤�′<����−1,�,�−∑�<�′≤�′≤����−1,�,�)dpi,l,r​=P(l,r)(1≤l≤r≤m∑​dpi−1,l,r​−1≤l′≤r′<l∑​dpi−1,l,r​−r<l′≤r′≤m∑​dpi−1,l,r​),如果我们记 ��,�Ri,r​ 为右端点为 �r 的 ���,�,�dpi,l,r​ 的和,��,�Li,l​ 为左端点为 �l 的 ���,�,�dpi,l,r​ 的和,那么我们可预处理 ��,�Ri,r​ 的前缀和与 ��,�Li,l​ 的后缀和,这样可实现 �(1)O(1) 转移,复杂度降到了 �3n3。

但这样还是会炸,事实上,我们状态数已经达到了 �(�3)O(n3),光是数组都远远开不下,因此考虑优化状态。我们考虑不计算 ���,�,�dpi,l,r​,直接计算 ��,�,��,�Ri,r​,Li,l​ 并写出它们的转移方程式。我们不妨进一步改写下 ���,�,�dpi,l,r​ 的转移方程式,将 ∑∑ 展开成关于 ��,�,��,�Li,l​,Ri,r​ 的表达式,那么有 ���,�,�=�(�−1)�(�−�)(∑�=1���−1,�−∑�=1�−1��−1,�−∑�=�+1���−1,�)dpi,l,r​=f(l−1)f(m−r)(j=1∑m​Ri−1,j​−j=1∑l−1​Ri−1,j​−j=r+1∑m​Li−1,j​)。还需注意到的一点是,容易发现 ��,�Li,l​ 的转移方程式与 ��,�Ri,r​ 高度相似,事实上如果我们把每一排翻转过来的话即可发现 ��,�Ri,r​ 变成了 ��,�Li,l​,因此我们可以得到 ��,�=��,�−�+1Li,l​=Ri,m−l+1​,故上述方程可进一步改写为 ���,�,�=�(�−1)�(�−�)(∑�=1���−1,�−∑�=1�−1��−1,�−∑�=1�−���−1,�)dpi,l,r​=f(l−1)f(m−r)(j=1∑m​Ri−1,j​−j=1∑l−1​Ri−1,j​−j=1∑m−r​Ri−1,j​),我们考虑直接将 ��,�,���,�,�Ri,r​,dpi,l,r​ 的方程式合并,即不经过 ���,�,�dpi,l,r​,直接根据 ��−1,�Ri−1,j​ 的值转移到 ��,�Ri,j​

��,�=∑�=1����,�,�=∑�=1��(�−1)�(�−�)(∑�=1���−1,�−∑�=1�−1��−1,�−∑�=1�−���−1,�)=�(�−�)((∑�=1��(�−1)(∑�=1���−1,�−∑�=1�−���−1,�))−∑�=1�∑�=1�−1�(�−1)��−1,�Ri,r​​=l=1∑r​dpi,l,r​=l=1∑r​f(l−1)f(m−r)(j=1∑m​Ri−1,j​−j=1∑l−1​Ri−1,j​−j=1∑m−r​Ri−1,j​)=f(m−r)((l=1∑r​f(l−1)(j=1∑m​Ri−1,j​−j=1∑m−r​Ri−1,j​))−l=1∑r​j=1∑l−1​f(l−1)Ri−1,j​​

emmm……推到这一步应该就非常好维护了吧。

记 ���,�=∑�=1���,�SRi,j​=r=1∑j​Ri,r​,即 ��,�Ri,j​ 的前缀和,那么 ��,�=�(�−�)((∑�=1��(�−1)(���−1,�−���−1,�−�))−∑�=1��(�−1)���−1,�−1Ri,r​=f(m−r)((l=1∑r​f(l−1)(SRi−1,m​−SRi−1,m−r​))−l=1∑r​f(l−1)SRi−1,l−1​

显然 ���−1,�−���−1,�−�SRi−1,m​−SRi−1,m−r​ 是常数,可 �(1)O(1) 求出,因此我们只需处理 �(�)f(j) 的前缀和和 �(�)���−1,�f(j)SRi−1,j​ 的前缀和即可实现 �(1)O(1) 转移。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
	#define FILE_SIZE 1<<23
	char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
	inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
	inline void putc(char x){(*p3++=x);}
	template<typename T> void read(T &x){
		x=0;char c=getchar();T neg=0;
		while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
		while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
		if(neg) x=(~x)+1;
	}
	template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
	template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
	void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1.5e3;
const int MAXK=1e5;
const int MOD=1e9+7;
int n,m,a,b,k,p,fac[MAXK+5],ifac[MAXK+5];
void init_fac(int n){
	fac[0]=ifac[0]=ifac[1]=1;
	for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
}
int d[MAXN+5],sd[MAXN+5],ss[MAXN+5];
int dp[MAXN+5][MAXN+5],sdp[MAXN+5][MAXN+5];
int binom(int x,int y){return 1ll*fac[x]*ifac[x-y]%MOD*ifac[y]%MOD;}
int qpow(int x,int e=MOD-2){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
int main(){
	scanf("%d%d%d%d%d",&n,&m,&a,&b,&k);
	p=1ll*a*qpow(b)%MOD;init_fac(k);
	if(p==1){
		if(k<=m) d[k]=1;
	}
	else{
		int ivp=1ll*p*qpow(MOD+1-p)%MOD,pw=1;
		for(int i=1;i<=k;i++) pw=1ll*pw*(MOD+1-p)%MOD;
		for(int i=0;i<=m;i++) d[i]=1ll*binom(k,i)*pw%MOD,pw=1ll*pw*ivp%MOD;
	}
	sd[0]=d[0];for(int i=1;i<=m;i++) sd[i]=(sd[i-1]+d[i])%MOD;
	dp[0][m]=1;sdp[0][m]=1;
	for(int i=1;i<=n;i++){
		memset(ss,0,sizeof(ss));
		for(int j=1;j<=m;j++) ss[j]=(ss[j-1]+1ll*d[j]*sdp[i-1][j])%MOD;
		for(int j=1;j<=m;j++){
			dp[i][j]=1ll*d[m-j]*(1ll*(sdp[i-1][m]-sdp[i-1][m-j]+MOD)*sd[j-1]%MOD-ss[j-1]+MOD)%MOD;
//			printf("%d %d %d\n",i,j,dp[i][j]);
		}
		for(int j=1;j<=m;j++) sdp[i][j]=(sdp[i][j-1]+dp[i][j])%MOD;
	} printf("%d\n",sdp[n][m]);
	return 0;
}

Description

给定一个 (�+2)×�(n+2)×m 的网格和三个整数 �,�,�a,b,k,除了第一行和最后一行,从第一天开始每一天每一行最左边和最右边的格子都有 �×�−1b×a−1 的概率消失,其中 �−1a−1 表示 �a 在模 109+7109+7 意义下的逆元。求在 �k 天网格保持连通的概率,答案对 109+7109+7 取模。

其中 1≤�,�≤1.5×1031≤n,m≤1.5×103,0≤�≤1050≤k≤105,1≤�,�≤1091≤a,b≤109 且 �,�a,b 互质。


Solution

考虑动态规划。设 �=�×�−1p=b×a−1。为了方便起见,我们先将网格整体上移一格,即原先的第 11 行变成第 00 行,原先的第 �+2n+2 行变成第 �+1n+1 行。第 00 行和第 �+1n+1 行的格子是不会消失的。

先考虑状态的设计。一种简单的想法是:设 ��,�fi,j​ 表示 �k 天后第 �i 行还剩 �j 个格子时整个网格仍保持连通的概率。但是这样的状态设计不仅很难转移,而且我们无法表示出每一行某种状态出现的概率,因为每一行只可能是最左边和最右边消失,我们并不知道左边或右边有多少个格子消失了。于是我们可以试着保留每一行的左端点和右端点:设 ��,�,�fi,l,r​ 表示 �k 天后第 �i 行还剩下 [�,�][l,r] 这一部分格子时整个网格仍然保持连通的概率。

这样就可以表示每一行某种状态出现的概率了。设每一行左边或右边在第 �k 天消失 �i 个格子的概率为 �(�)P(i)。由于左边和右边的格子的消失是相互独立的事件,两边在第 �k 天消失 �i 个的概率是相等的。有:

�(�)=���×��×(1−�)�−�P(i)=Cki​×pi×(1−p)k−i

有 �i 天消失,概率就是 ��pi,�−�k−i 天不消失,概率就是 (1−�)�−�(1−p)k−i。 �k 天当中可以任意有 �i 天消失,因此乘以 ���Cki​。这样,每一行剩下 [�,�][l,r] 的概率就是 �(�−1)×�(�−�)P(l−1)×P(m−r)。可以先预处理出所有的 �(�)P(i)

考虑状态的转移。由于要让网格连通,显然第 �−1i−1 行剩下的格子所处的位置要与第 �i 行剩下的格子所处的位置有相交的部分。设第 �−1i−1 行剩下 [�′,�′][l′,r′] 这部分格子。那么有 [�,�]∪[�′,�′]≠∅[l,r]∪[l′,r′]=∅。状态转移方程:

��,�,�=�(�−1)×�(�−�)×(∑[�,�]∪[�′,�′]≠∅��−1,�′,�′)fi,l,r​=P(l−1)×P(m−r)×([l,r]∪[l′,r′]=∅∑​fi−1,l′,r′​)

初态:�0,1,�=1f0,1,m​=1。最终答案就是 ∑�=1�∑�=����,�,�l=1∑m​r=l∑m​fn,l,r​。

这样一来,我们就得到了一个时间复杂度 �(��4)O(nm4) 附带空间复杂度 �(��2)O(nm2) 的优秀算法,可以拿下 Unaccepted。

发现这个式子并不好优化。试着转化一下:将“状态有相交部分的总和”转化为“所有状态的总和减去没有相交部分的状态”。也就是

��,�,�fi,l,r​=�(�−1)×�(�−�)×(∑[�,�]∪[�′,�′]≠∅��,�′,�′)=P(l−1)×P(m−r)×([l,r]∪[l′,r′]=∅∑​fi,l′,r′​)=�(�−1)×�(�−�)×(∑�′=1�∑�′=�′���−1,�′,�′−∑�′=1�−1∑�′=�′�−1��−1,�′,�′−∑�′=�+1�∑�′=�′���−1,�′,�′)=P(l−1)×P(m−r)×(l′=1∑m​r′=l′∑m​fi−1,l′,r′​−l′=1∑l−1​r′=l′∑l−1​fi−1,l′,r′​−l′=r+1∑m​r′=l′∑m​fi−1,l′,r′​)

先来尝试一下优化空间。我们试着把 ��,�,�fi,l,r​ 中 �l 这一维去掉,也就是只保留右端点而枚举左端点。此时 ��,�fi,r​ 表示第 �i 行还剩下右端点为 �r 的格子时整个网格仍然保持连通的概率。这样上面的状态转移方程中 ∑�′=1�∑�′=�′���−1,�′,�′l′=1∑m​r′=l′∑m​fi−1,l′,r′​ 这一部分的计算变成了 ∑�′=1���−1,�′r′=1∑m​fi−1,r′​,∑�′=1�−1∑�′=�′�−1��−1,�′,�′l′=1∑l−1​r′=l′∑l−1​fi−1,l′,r′​ 这一部分的计算变成了 ∑�′=1�−1��−1,�′r′=1∑l−1​fi−1,r′​。但是问题来了:∑�′=�+1�∑�′=�′���−1,�′,�′l′=r+1∑m​r′=l′∑m​fi−1,l′,r′​ 怎么计算?

注意到本题有一个性质:��,�,�=��,�−�+1,�−�+1fi,l,r​=fi,m−r+1,m−l+1​。由于两边对称,这条性质正确性显然。因此 ∑�′=�+1�∑�′=�′���−1,�′,�′=∑�′=1�−�∑�′=�′�−���−1,�′,�′=∑�′=1�−���−1,�′l′=r+1∑m​r′=l′∑m​fi−1,l′,r′​=l′=1∑m−r​r′=l′∑m−r​fi−1,l′,r′​=r′=1∑m−r​fi−1,r′​。

新的状态转移方程:

��,�=∑�=1��(�−1)×�(�−�)×(∑�′=1���−1,�′−∑�′=1�−1��−1,�′−∑�′=1�−���−1,�′)fi,r​=l=1∑r​P(l−1)×P(m−r)×(r′=1∑m​fi−1,r′​−r′=1∑l−1​fi−1,r′​−r′=1∑m−r​fi−1,r′​)

时间复杂度 �(��3)O(nm3),空间复杂度 �(��)O(nm),已经有很大进步了。

考虑继续优化。注意到 ∑�′=1���−1,�′−∑�′=1�−1��−1,�′−∑�′=1�−���−1,�′r′=1∑m​fi−1,r′​−r′=1∑l−1​fi−1,r′​−r′=1∑m−r​fi−1,r′​ 的值可以通过求 ��−1fi−1​ 的前缀和求出。设 ����=∑�=1���−1,�sumj​=k=1∑j​fi−1,k​,状态转移方程:

��,�=∑�=1��(�−1)×�(�−�)×(����−����−1−����−�)fi,r​=l=1∑r​P(l−1)×P(m−r)×(summ​−suml−1​−summ−r​)

时间复杂度减少到了 �(��2)O(nm2),但还是不足以通过。

显然 �i 和 �r 是我们不得不去枚举的,因此尝试在 �l 的枚举上做点文章,试一下能不能把 �l 的枚举省去。先把状态转移方程中有关 �l 和有关 �r 的部分拆开:

��,�=∑�=1��(�−1)×�(�−�)×(����−����−1−����−�)fi,r​=l=1∑r​P(l−1)×P(m−r)×(summ​−suml−1​−summ−r​)=�(�−�)(∑�=1��(�−1)×(����−����−1−����−�))=P(m−r)(l=1∑r​P(l−1)×(summ​−suml−1​−summ−r​))=�(�−�)(∑�=1��(�−1)×(����−����−�)−∑�=1��(�−1)×����−1)=P(m−r)(l=1∑r​P(l−1)×(summ​−summ−r​)−l=1∑r​P(l−1)×suml−1​)=�(�−�)((����−����−�)(∑�=1��(�−1))−∑�=1��(�−1)×����−1)=P(m−r)((summ​−summ−r​)(l=1∑r​P(l−1))−l=1∑r​P(l−1)×suml−1​)

这时我们惊奇地发现,∑�=1��(�−1)l=1∑r​P(l−1) 和 ∑�=1��(�−1)×����−1l=1∑r​P(l−1)×suml−1​ 这两个求和式中的元素都只和 �l 有关。又可以使用前缀和优化了!

设 �����=∑�=0��(�)sumpi​=j=0∑i​P(i),�����=∑�=1��(�)×����ssumi​=j=1∑i​P(i)×sumi​。由于 ���0=0sum0​=0,所以可以不用把 �(0)×���0P(0)×sum0​ 算进去。最终的状态转移方程:

=�(�−�)((����−����−�)(�����−1)−�����−1)=P(m−r)((summ​−summ−r​)(sumpr−1​)−ssumr−1​)

初态:�0,�=1f0,m​=1。最终答案即为 ∑�=1���,�r=1∑m​fn,r​

时间复杂度和空间复杂度均为 �(��)O(nm),已经足以通过本题了。


Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
const int N=1.5e3+10,M=1e5+10;
int f[N][N];
int P[M],fac[M],sum[N],sump[N],ssum[N];
int qpow(int a,int b)
{
	int res=1%mod;a%=mod;
	for(;b;b>>=1)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
	}
	return res;
}
int C(int n,int m)
{
	if(n<m) return 0;
	return fac[n]*qpow(fac[m],mod-2)%mod*qpow(fac[n-m],mod-2)%mod;
}
signed main()
{
	int n,m,a,b,K;
	scanf("%lld%lld%lld%lld%lld",&n,&m,&a,&b,&K);
	fac[0]=1;for(int i=1;i<=K;i++) fac[i]=fac[i-1]*i%mod;
	int p=a*qpow(b,mod-2)%mod,p1=(b-a)*qpow(b,mod-2)%mod;
	for(int i=0;i<=K;i++) P[i]=C(K,i)*qpow(p,i)%mod*qpow(p1,K-i)%mod;
	sump[0]=P[0];for(int i=1;i<=m;i++) sump[i]=(sump[i-1]+P[i])%mod;
	f[0][m]=1;
	for(int i=1;i<=n;i++)
	{
		sum[0]=0,ssum[0]=0;
		for(int r=1;r<=m;r++) sum[r]=(sum[r-1]+f[i-1][r])%mod,ssum[r]=(ssum[r-1]+sum[r]*P[r])%mod;
		for(int r=1;r<=m;r++)
			f[i][r]=P[m-r]*((sum[m]-sum[m-r]+mod)%mod*sump[r-1]%mod-ssum[r-1]%mod+mod)%mod;
	}
	int ans=0;
	for(int r=1;r<=m;r++)
			ans=(ans+f[n][r])%mod;
	printf("%lld\n",ans);
	return 0;
}

考虑 �(�,�,�)f(n,x,y) 表示第 �n 行左边被删了 �x 个右边被删了 �y 个的概率。

考虑用所有方案减去不连通的,�(�,�)g(x,y) 表示某行左边被删 �x 个右边被删 �y 个的的概率,�(�)=∑�+�<��(�,�,�)S(n)=∑x+y<m​f(n,x,y)有:

�(�,�,�)=�(�,�)(�(�−1)−∑�≥�−� or �≥�−��(�−1,�,�))f(n,x,y)=g(x,y)(S(n−1)−Y≥m−x or X≥m−y∑​f(n−1,X,Y))

容易发现 �g 的定义中左边和右边没有任何关系,所以可以写成 �(�,�)=�(�)�(�)g(x,y)=P(x)P(y),稍微写写有:

�(�)=(��)��(1−�)�−�P(n)=(nk​)pn(1−p)k−n

容易发现 �f 的转移具有对称性,这样会导致 �(�,�,�)=�(�,�,�)f(n,x,y)=f(n,y,x)。(�g 也一样)

设 �(�,�)=∑�≥��(�,�,�)F(n,k)=∑x≥k​f(n,x,y)。

可以重写转移:

�(�,�,�)=�(�)�(�)(�(�−1)−�(�−1,�−�)−�(�−1,�−�))f(n,x,y)=P(x)P(y)(S(n−1)−F(n−1,m−x)−F(n−1,m−y))

考虑对 �F 进行转移而不是 �f。

容易发现 �(�)S(n) 相当于 �(�,0)F(n,0)。

重新设 �(�,�)=∑�=��(�−1,�,�)F(n,k)=∑y=k​f(n−1,x,y),这样就只需要最后处理一个后缀和即可。

�(�,�)=∑�=0�−�−1�(�)�(�)(�(�−1)−�(�−1,�−�)−�(�−1,�−�))F(n,x)=y=0∑m−x−1​P(x)P(y)(S(n−1)−F(n−1,m−x)−F(n−1,m−y))=�(�)(�(�−1)−�(�−1,�−�))∑�=0�−�−1�(�)−�(�)∑�=0�−�−1�(�)�(�−1,�−�)=P(x)(S(n−1)−F(n−1,m−x))y=0∑m−x−1​P(y)−P(x)y=0∑m−x−1​P(y)F(n−1,m−y)

随便前缀和优化一下即可,复杂度 �(��)O(nm)。

初始值 �(0,0,0)=1F(0,0,0)=1,答案为 �(�)S(n)。

题外话,这题不值 *3100 吧。

 

#include<cstdio>
namespace SOLVE{
	const int M=1505,mod=1e9+7;
	int n,m,k,p,P[M],F[M],G[M],S1[M],S2[M];
	inline int pow(int a,int b=mod-2){
		int ans(1);for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ans=1ll*ans*a%mod;return ans;
	}
	inline void main(){
		scanf("%d%d%d%d%d",&n,&m,S1,S2,&k);p=1ll*S1[0]*pow(S2[0])%mod;S1[0]=pow(mod+1-p,k);S2[0]=0;
		const int&q=1ll*p*pow(mod+1-p)%mod;for(int i=1;i<m;++i)S1[i]=1ll*S1[i-1]*q%mod;
		if(2*k<m)return printf("1"),void();if(p==1)return printf("0"),void();
		P[0]=P[1]=1;for(int i=2;i<m&&i<=k;++i)P[i]=1ll*(mod-mod/i)*P[mod%i]%mod;
		for(int i=1;i<m&&i<=k;++i)P[i]=1ll*(k-i+1)*P[i]%mod*P[i-1]%mod;
		for(int i=0;i<m;++i)P[i]=1ll*P[i]*S1[i]%mod;S1[0]=P[0];F[0]=1;
		for(int i=1;i<m;++i)S1[i]=(S1[i-1]+P[i])%mod;
		for(int k=1;k<=n;++k){
			const int&S(F[0]);for(int i=1;i<m;++i)S2[i]=(S2[i-1]+1ll*(mod-P[i])*F[m-i])%mod;
			for(int i=0;i<m;++i)G[i]=(1ll*(S+mod-F[m-i])*S1[m-i-1]+S2[m-i-1])%mod*P[i]%mod;
			for(int i=m-1;i>=0;--i)F[i]=(G[i]+F[i+1])%mod,G[i]=0;
		}
		printf("%d",F[0]);
	} 
}
signed main(){
	SOLVE::main();
}

温馨提示:上面的只是开胃小菜,这把高端局!!!:

Title Description: 

Alex studied well and won the trip to student camp Alushta, located on the seashore.

Unfortunately, it's the period of the strong winds now and there is a chance the camp will be destroyed! Camp building can be represented as the rectangle of �+2n+2 concrete blocks height and �m blocks width.

Every day there is a breeze blowing from the sea. Each block, except for the blocks of the upper and lower levers, such that there is no block to the left of it is destroyed with the probability 

. Similarly, each night the breeze blows in the direction to the sea. Thus, each block (again, except for the blocks of the upper and lower levers) such that there is no block to the right of it is destroyed with the same probability �p . Note, that blocks of the upper and lower level are indestructible, so there are only �⋅�n⋅m blocks that can be destroyed.

The period of the strong winds will last for �k days and �k nights. If during this period the building will split in at least two connected components, it will collapse and Alex will have to find another place to spend summer.

Find the probability that Alex won't have to look for other opportunities and will be able to spend the summer in this camp.

输入格式

The first line of the input contains two integers �n and �m ( 1<=�,�<=15001<=n,m<=1500 ) that define the size of the destructible part of building.

The second line of the input contains two integers �a and �b ( 1<=�<=�<=1091<=a<=b<=109 ) that define the probability �p . It's guaranteed that integers �a and �b are coprime.

The third line contains a single integer �k ( 0<=�<=1000000<=k<=100000 ) — the number of days and nights strong wind will blow for.

输出格式

Consider the answer as an irreducible fraction is equal to 

. Print one integer equal to 

. It's guaranteed that within the given constraints 

.

题意翻译

有一个 (�+2)×�(n+2)×m 的网格。

除了第一行和最后一行,其他每一行每一天最左边和最右边的格子都有 �p 的概率消失。

求 �k 天后,网格始终保持连通的概率。

�,�≤1.5×103n,m≤1.5×103,�≤105k≤105,答案对 109+7109+7 取模。

输入输出样例

输入 #1复制

2 2
1 2
1

输出 #1复制

937500007

输入 #2复制

5 1
3 10
1

输出 #2复制

95964640

输入 #3复制

3 3
1 10
5

输出 #3复制

927188454

说明/提示

In the first sample, each of the four blocks is destroyed with the probability 

. There are 77 scenarios that result in building not collapsing, and the probability we are looking for is equal to 

, so you should print 

Submit

2.52k

adopt

1.09k

time limit

3.00s

Memory limit

250.00MB

Firstly, translate the meaning of the question. The question has already specified that the blocks in the highest and lowest rows will not be blown away, so the entire building can only be "disconnected" by being "split up and down", not "split left and right". That is to say, the entire building is' connected 'if and only if, for all adjacent rows, their last remaining blocks are connected.

Consider DP. set up

(

,

,

)

F (i, l, r) represents the th

The remaining i line

[

,

]

The probability that the block of [l, r] and the building will not collapse.

First, consider the remaining row

[

,

]

The probability of the block of [l, r].

set up

(

)

D (i) represents

During the 'k blows'

The probability of success i times, obviously

(

)

=

(

)

(

one

)

D(i)=(

i

k

 )p

i

 (1−p)

k−i

sure

(

)

O (k) Preprocess to find all

(

)

D (i).

set up

(

,

)

(

one

)

P (l, r) (1 ≤ l ≤ r ≤ m) indicates that only one row remains

[

,

]

The probability of bricks of [l, r], due to the independence of the left and right sides, can be obtained

(

,

)

=

(

one

)

(

)

P(l,r)=D(l−1)D(m−r)

Next, consider how to proceed from the

one

I-1 line transfer.

Based on the previous analysis, the

The interval occupied by the remaining block at the end of line i should be the same as that of line i

one

The interval between lines i and 1 intersects, that is

(

,

,

)

=

(

,

)

[

,

]

[

,

]

(

one

,

,

)

f(i,l,r)=P(l,r)

[l

 ,r

 ]∩[l,r]

=∅

 f(i−1,l

 ,r

 )

The boundary is

(

0

,

one

,

)

=

one

F (0,1, m)=1, the answer is all

(

,

,

)

The sum of f (n, l, r).

The number of states in this way is

(

two

)

O(nm

two

)Transfer complexity

(

two

)

O(m

two

)It needs optimization.

Consider the opposite of the problem, that is

[

,

]

[l, r] and

[

,

]

[l

 ,r

]There is no intersection. It is easy to see that there are only two mutually exclusive situations: either

<

r

<l, or

>

l

>R.

So, we obtained:

(

,

,

)

=

(

,

)

[

(

one

,

,

)

<

(

one

,

,

)

>

(

one

,

,

)

]

f(i,l,r)=P(l,r)[

l

 ≤r

 f(i−1,l

 ,r

 )−

r

 <l

 f(i−1,l

 ,r

 )−

l

 >r

 f(i−1,l

 ,r

 )]

It was found that only the part requiring three summation numbers can be transferred, that is

(

)

=

(

,

,

)

(

,

)

=

<

(

,

,

)

(

,

)

=

>

(

,

,

)

(

,

,

)

=

(

,

)

[

(

one

)

(

one

,

)

(

one

,

)

]

F(i)

L(i,x)

R(i,x)

f(i,l,r)

=

l≤r

 f(i,l,r)

=

l≤r<x

 f(i,l,r)

=

r≥l>x

 f(i,l,r)

=P(l,r)[F(i−1)−L(i−1,l)−R(i−1,r)]

Pay attention to it

(

)

The meaning of F (i), which represents the former

The probability of i-row connectivity, then the answer is

(

)

F (n).

Observant classmates may have already noticed,

(

,

)

L (i, x) and

(

,

)

R (i, x) is highly symmetrical in terms of transfer, form, and outcome. In fact, to

(

,

)

Flip L (i, x) left and right to obtain

(

,

)

R (i, x), i.e

(

,

)

=

(

,

+

one

)

L (i, x)=R (i, m − x+1). Therefore, we only need to discuss

(

,

)

The processing of L (i, x) is sufficient.

because

(

,

,

)

F (i, l, r) will affect all

>

x> R's

(

,

)

L (i, x) contributes, we can first note

(

,

)

S

748 / 2000

We will number the rows as

0

+

one

From 0 to n+1, for a connected state, there must exist a path from th to th

0

From line 0 to line

+

one

A path in line n+1 that only travels in the left, right, and bottom directions, passing through undamaged blocks along the way. In order to make each connected state correspond to a unique path, we force this path:

If you can walk down, just go down;

Otherwise, walk left or right to the nearest block that can go down, and then go down.

Considering DP, let's assume

,

f

i,j

Indicating from the

one

I-1 line enters the first line

At line i, in the

Column J, and in the th column

one

When going to the right in line i-1, the

one

one

The probability corresponding to the destruction state of lines 1 to 1;

,

g

i,j

The definition is to change the phrase 'go to the right' to 'go to the left';

,

h

i,j

The definition is to change 'going to the right' to 'not moving left or right'.

f

i

By

one

,

one

f

i−1

 ,h

i−1

Transferred,

g

i

By

one

,

one

g

i−1

 ,h

i−1

Transferred,

h

i

By

one

,

one

,

one

f

i−1

 ,g

i−1

 ,h

i−1

Transferred over. Calculate the

When calculating the DP value of line i, we include the th

one

The probability corresponding to the destruction state of line i-1. Please ask the reader to deduce the transfer equation themselves (fog).

Transfer can be prefixed and optimized, with time complexity

(

)

O (nm).

According to this idea of "path", there should be a simpler DP method for readers to think about on their own. Specifically, we can adjust the method of 'forcing each connected state to correspond to a unique path'. I just followed the DP method I got when I first thought of this idea.

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int p=1000000007;
int n,m,x,y,q,K,a[1505],s[1505],*f,*F,*g,*G,*h,*H,sum,ans;
int f1[1505],f2[1505],g1[1505],g2[1505],h1[1505],h2[1505];
inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=(ll)res*a%p;
		b>>=1,a=(ll)a*a%p;
	}
	return res;
}
int main(){
	scanf("%d%d%d%d%d",&n,&m,&x,&y,&K);
	q=(ll)x*qpow(y,p-2)%p;
	for(int i=0,C=1;i<=m;++i){
		if(i<=K)a[i]=(ll)C*qpow(q,i)%p*qpow(p+1-q,K-i)%p;
		else a[i]=0;
		s[i]=((i?s[i-1]:0)+a[i])%p;
		C=(ll)C*(K-i)%p*qpow(i+1,p-2)%p;
	}
	f=f1,F=f2,g=g1,G=g2,h=h1,H=h2;
	for(int j=1;j<=m;++j)f[j]=1;
	for(int i=2;i<=n;++i){
		sum=0;
		for(int j=1;j<=m;++j){
			F[j]=(ll)sum*s[m-j]%p;
			sum=(sum+(ll)f[j]*a[j-1]+(ll)h[j]*s[j-1])%p;
			H[j]=((ll)f[j]*a[j-1]%p*s[m-j]+(ll)g[j]*a[m-j]%p*s[j-1]+(ll)h[j]*s[j-1]%p*s[m-j])%p;
		}
		sum=0;
		for(int j=m;j>=1;--j){
			G[j]=(ll)sum*s[j-1]%p;
			sum=(sum+(ll)g[j]*a[m-j]+(ll)h[j]*s[m-j])%p;
		}
		swap(f,F),swap(g,G),swap(h,H);
	}
	for(int j=1;j<=m;++j)
		ans=(ans+(ll)f[j]*a[j-1]%p*s[m-j]+(ll)g[j]*a[m-j]%p*s[j-1]+(ll)h[j]*s[j-1]%p*s[m-j])%p;
	printf("%d\n",ans);
	return 0;
}

2082 / 2000

One

(

+

two

)

×

A grid of (n+2) × m.

Except for the first and last lines, each line has

The probability of p disappearing, find

The probability that the grid will remain connected after k days.

The answer is correct

one

0

nine

+

seven

ten

nine

+Take 7 molds.

Data Range:

one

,

one point five

×

one

0

three

,

one

0

five

Data Range:1≤n,m≤1.5×10

three

 ,k≤10

five

.

Come to my blog for a better reading experience.

It is not difficult to find that the remaining rows in the end will always be a continuous interval, and each row is independent of each other.

Natural establishment status

,

,

dp

i,l,r

Indicates that the current position is

I line,

After k days, the remaining interval in this row is

[

,

]

[l, r], th

0

From line 0 to line

The probability of connecting rows i.

Consider converting it into an interval first

[

,

]

The probability of [l, r].

set up

Within k days, disappear

The probability of i cells is

=

(

)

×

(

one

)

g

i

 =(

i

k

 )p

i

 ×(1−p)

k−i

.

So the remaining interval

[

,

]

The probability of [l, r] is

one

×

g

l−1

 ×g

m−r

.

Of course, it is also necessary to check if the length of this interval is legal, as it can disappear at most

two

2k grids.

The transfer equation is not difficult to write.

,

,

=

one

×

[

one

,

one

]

[

,

]

one

,

one

,

one

dp

i,l,r

 =g

l−1

 ×g

m−r

[l

one

 ,r

one

 ]∩[l,r]

=∅

 dp

i−1,l

one

 ,r

one

Complexity

four

nm

four

Obviously not, and the status numbers are all

two

nm

two

.

Consider optimizing the state and removing the constraint on the left endpoint.

set up

,

dp

i,r

Indicate the

The right endpoint of the remaining interval in line i is

r, The

0

From line 0 to line

The probability of connecting rows i is enumerated during transition

Perform transfer.

Consider a and interval

[

,

]

[l, r] intervals with intersections

[

one

,

one

]

[l

one

 ,r

one

].

It satisfies

one

,

one

r

one

 ≥l,l

one

If it is ≤ r, then Rongqi can calculate this part.

about

one

<

r

one

The part of<l, enumeration

Calculate at L time

=

one

one

one

,

j=1

l−1

 dp

i−1,j

.

about

one

>

l

one

>The part of r, because the grid is symmetrical

,

f

i,r

It can also indicate that the left endpoint is

+

one

The probability of m − r+1 being connected, therefore this part can be calculated as

=

one

one

,

j=1

m−r

 f

i−1,j

.

So the transfer is as follows.

,

=

=

one

one

(

=

one

one

,

=

one

one

one

,

=

one

one

,

)

dp

i,r

 =

l=1

r

 g

l−1

 g

m−r

 (

j=1

m

 f

i−1,j

 −

j=1

i−1

 f

i−1,j

 −

j=1

m−r

 f

i−1,j

 )

This thing looks very prefixed, so consider prefix optimization.

order

s

j

Expressing

=

one

one

,

k=1

j

 f

i−1,k

.

,

=

=

one

one

)

(

one

)

f

i,r

 =g

m−r

l=1

r

 g

l−1

 )(s

m

 −s

l−1

 −s

m−r

 )

,

=

×

(

(

)

=

one

one

=

one

one

one

)

f

i,r

 =g

m−r

 ×((s

m

 −s

m−r

 )

l=1

r

 g

l−1

 −

l=1

r

 g

l−1

 s

l−1

 )

In order to

p

i

And

q

i

In sequence, they are

g

i

#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
  int x = 0, f = 1;  char ch = getchar();
  while( !isdigit(ch) ) { if(ch == '-') f = -1;  ch = getchar();  }
  while( isdigit(ch) ) {  x = (x << 1) + (x << 3) + (ch ^ 48);  ch = getchar();  }
  return x * f;
}
const int N = 3e6, mod = 1e9 + 7;
int n, m, a, b, pt, pt2, k, ans, fac[N], ifac[N], dp[3005][3005], g[3005];
int power(int a,int b) { int ans = 1; while (b) { if (b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1;} return ans; }
int binom(int n,int m) { return fac[n] * ifac[m] % mod * ifac[n - m] % mod; }
int s[N], p[N], q[N];
signed main () {
  n = read(), m = read(), a = read(), b = read(), k = read(); 
  fac[0] = 1; for (int i = 1; i <= k; i++) fac[i] = fac[i - 1] * i % mod;
  ifac[k] = power(fac[k], mod - 2); for (int i = k - 1; ~i; i--) ifac[i] = ifac[i + 1] * (i + 1) % mod;
  pt = a * power(b, mod - 2) % mod; pt2 = 1 - pt; pt2 += mod; pt2 %= mod;
  for (int i = 0; i <= k && i <= m; i++) g[i] = binom(k, i) * power(pt, i) % mod * power(pt2, k - i) % mod;
  dp[0][m] = 1;  p[0] = g[0]; 
  for (int i = 1; i <= m; i++) p[i] = p[i - 1] + g[i], p[i] %= mod;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) s[j] = s[j - 1] + dp[i - 1][j], s[j] %= mod;
    for (int j = 1; j <= m; j++) q[j] = q[j - 1] + g[j] * s[j] % mod, q[j] %= mod;
    for (int r = 1; r <= m; r++) dp[i][r] = g[m - r] * ( (s[m] - s[m - r] + mod) % mod * p[r - 1] % mod - q[r - 1] + mod) % mod;
  }  
  for (int i = 1; i <= m; i++) { ans += dp[n][i]; ans %= mod;}
  printf("%lld\n", ans);
  return 0;
}

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int p=1000000007;
int n,m,x,y,q,K,a[1505],s[1505],*f,*F,*g,*G,*h,*H,sum,ans;
int f1[1505],f2[1505],g1[1505],g2[1505],h1[1505],h2[1505];
inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=(ll)res*a%p;
		b>>=1,a=(ll)a*a%p;
	}
	return res;
}
int main(){
	scanf("%d%d%d%d%d",&n,&m,&x,&y,&K);
	q=(ll)x*qpow(y,p-2)%p;
	for(int i=0,C=1;i<=m;++i){
		if(i<=K)a[i]=(ll)C*qpow(q,i)%p*qpow(p+1-q,K-i)%p;
		else a[i]=0;
		s[i]=((i?s[i-1]:0)+a[i])%p;
		C=(ll)C*(K-i)%p*qpow(i+1,p-2)%p;
	}
	f=f1,F=f2,g=g1,G=g2,h=h1,H=h2;
	for(int j=1;j<=m;++j)f[j]=1;
	for(int i=2;i<=n;++i){
		sum=0;
		for(int j=1;j<=m;++j){
			F[j]=(ll)sum*s[m-j]%p;
			sum=(sum+(ll)f[j]*a[j-1]+(ll)h[j]*s[j-1])%p;
			H[j]=((ll)f[j]*a[j-1]%p*s[m-j]+(ll)g[j]*a[m-j]%p*s[j-1]+(ll)h[j]*s[j-1]%p*s[m-j])%p;
		}
		sum=0;
		for(int j=m;j>=1;--j){
			G[j]=(ll)sum*s[j-1]%p;
			sum=(sum+(ll)g[j]*a[m-j]+(ll)h[j]*s[m-j])%p;
		}
		swap(f,F),swap(g,G),swap(h,H);
	}
	for(int j=1;j<=m;++j)
		ans=(ans+(ll)f[j]*a[j-1]%p*s[m-j]+(ll)g[j]*a[m-j]%p*s[j-1]+(ll)h[j]*s[j-1]%p*s[m-j])%p;
	printf("%d\n",ans);
	return 0;
}

如果你能看到这里,在评论区发个666,考验耐心的朋友;

其实我是想拿质量分,求CSDN的系统能给我审核通过,感谢^_^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值