CF708E Student's Camp
Student's Camp
有一个 \((n + 2) × m\) 的长方形,除了第一行和最后一行,其他每一行每一天最左边和最右边未被摧毁的格子都有概率 \(P\) 被摧毁(每天概率相同),每行之间独立且左边和右边独立,求 \(k\) 天之后最上面一行与最下面一行四联通的概率。
答案对 \(10^9 + 7\) 取模。
\(n, m ≤ 1500\)。
题解
https://www.luogu.com.cn/blog/hotwords/solution-cf708e
首先转化题意。题目中已经限定了最高和最低一行的方块不会被吹走,所以整个建筑「不连通」只可能是被「上下劈开」,不可能是被「左右劈开」。也就是说,整个建筑「连通」,当且仅当对于所有相邻的两行,它们最后剩下的方块连通。
考虑 DP。设 \(f(i,l,r)\) 表示第 \(i\) 行剩下 \([l,r]\) 的方块且建筑不倒的概率。
先考虑某一行剩下 \([l,r]\) 的方块的概率。
设 \(D(i)\) 表示 \(k\) 次「吹风」中 \(i\) 次成功的概率,显然
可以 \(O(k)\) 预处理求出所有的 \(D(i)\) 。
设 \(P(l,r)\ (1 \le l \le r \le m)\) 表示某一行只剩下 \([l,r]\) 的砖块的概率,由于左右两边相互独立,可以得到 $$ P(l,r) = D(l-1) D(m-r) $$
接下来考虑如何从第 \(i-1\) 行转移。
由前面的分析,第 \(i\) 行最后剩下的方块所占的区间要和第 \(i-1\) 行的区间有交,即
边界是 \(f(0,1,m) = 1\) ,答案即为所有 \(f(n,l,r)\) 的和。
这样状态数为 \(O(nm^2)\) ,转移复杂度 \(O(m^2)\) ,需要优化。
考虑问题的反面,即 \([l,r]\) 和 \([l',r']\) 没有交集。容易发现,这只有两种互斥的情况:要么 \(r' \lt l\) ,要么 \(l' \gt r\) 。
于是,我们得到:
发现只要求出三个求和号的部分就能转移了,即
注意其中 \(F(i)\) 的意义,它表示前 \(i\) 行连通的概率,那么答案就是 \(F(n)\) 。
细心的同学可能已经发现了, \(L(i,x)\) 和 \(R(i,x)\) 在转移、形式和结果上都是高度对称的。事实上,把 \(L(i,x)\) 左右翻转就可以得到 \(R(i,x)\) ,即 \(L(i,x)=R(i,m-x+1)\) 。于是我们只要讨论 \(L(i,x)\) 的处理就可以了。
由于 \(f(i,l,r)\) 会对所有 \(x \gt r\) 的 \(L(i,x)\) 产生贡献,我们可以先记 \(S_L(i,r)\) 表示所有右端点为 \(r\) 的 \(f(i,l,r)\) 之和,则对 \(S_L\) 计算前缀和即可得到 \(L\) ,用公式表达就是:
同时我们可以发现,所有的 \(S_L(i,r)\) 之和就等于 \(F(i)\) ,这样就把 \(F(i)\) 也一并解决了。
接下来考虑如何计算 \(S_L(i,r)\) ,把 \(f(i,l,r)\) 的转移式代入:
到这里思路已经很清晰了,对 \(D(l-1)\) 和 \(D(l-1) L(i-1,l)\) 做前缀和即可实现 \(O(1)\) 转移,最后答案即为 \(F(n)\) 。边界同样是 \(f(0,1,m)=1\) ,即 \(S_L(0,m)=1\) 。
总时间复杂度 \(O(nm+k)\) ,空间复杂度可以用滚动数组优化至 \(O(m+k)\) 。
CO int N=1.5e3+10,M=1e5+10;
int fac[M],ifac[M];
int D[N],sumD[N];
int f[N][N];
IN int C(int n,int m){
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int main(){
int n=read<int>(),m=read<int>();
int A=read<int>(),B=read<int>(),P=mul(A,fpow(B,mod-2));
int K=read<int>();
fac[0]=1;
for(int i=1;i<=K;++i) fac[i]=mul(fac[i-1],i);
ifac[K]=fpow(fac[K],mod-2);
for(int i=K-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
for(int i=0;i<=m and i<=K;++i)
D[i]=mul(C(K,i),mul(fpow(P,i),fpow(1+mod-P,K-i)));
sumD[0]=D[0];
for(int i=1;i<=m;++i) sumD[i]=add(sumD[i-1],D[i]);
f[0][m]=1;
for(int i=1;i<=n;++i){
int tot=f[i-1][m];
int DL=0;
for(int j=1;j<=m;++j){
DL=add(DL,mul(D[j-1],f[i-1][j-1]));
int tmp=add(mul(tot+mod-f[i-1][m-j],sumD[j-1]),mod-DL);
f[i][j]=add(f[i][j-1],mul(D[m-j],tmp));
}
}
printf("%d\n",f[n][m]);
return 0;
}