CF708E Student's Camp 题解
Description
给定一个 \((n+2)\times m\) 的网格和三个整数 \(a,b,k\),除了第一行和最后一行,从第一天开始每一天每一行最左边和最右边的格子都有 \(b\times a^{-1}\) 的概率消失,其中 \(a^{-1}\) 表示 \(a\) 在模 \(10^9+7\) 意义下的逆元。求在 \(k\) 天网格保持连通的概率,答案对 \(10^9+7\) 取模。
其中 \(1\le n,m\le1.5\times10^3\),\(0\le k\le10^5\),\(1\le a,b\le10^9\) 且 \(a,b\) 互质。
Solution
考虑动态规划。设 \(p=b\times a^{-1}\)。为了方便起见,我们先将网格整体上移一格,即原先的第 \(1\) 行变成第 \(0\) 行,原先的第 \(n+2\) 行变成第 \(n+1\) 行。第 \(0\) 行和第 \(n+1\) 行的格子是不会消失的。
先考虑状态的设计。一种简单的想法是:设 \(f_{i,j}\) 表示 \(k\) 天后第 \(i\) 行还剩 \(j\) 个格子时整个网格仍保持连通的概率。但是这样的状态设计不仅很难转移,而且我们无法表示出每一行某种状态出现的概率,因为每一行只可能是最左边和最右边消失,我们并不知道左边或右边有多少个格子消失了。于是我们可以试着保留每一行的左端点和右端点:设 \(f_{i,l,r}\) 表示 \(k\) 天后第 \(i\) 行还剩下 \([l,r]\) 这一部分格子时整个网格仍然保持连通的概率。
这样就可以表示每一行某种状态出现的概率了。设每一行左边或右边在第 \(k\) 天消失 \(i\) 个格子的概率为 \(P(i)\)。由于左边和右边的格子的消失是相互独立的事件,两边在第 \(k\) 天消失 \(i\) 个的概率是相等的。有:
有 \(i\) 天消失,概率就是 \(p^i\),\(k-i\) 天不消失,概率就是 \((1-p)^{k-i}\)。 \(k\) 天当中可以任意有 \(i\) 天消失,因此乘以 \(C_{k}^{i}\)。这样,每一行剩下 \([l,r]\) 的概率就是 \(P(l-1)\times P(m-r)\)。可以先预处理出所有的 \(P(i)\)
考虑状态的转移。由于要让网格连通,显然第 \(i-1\) 行剩下的格子所处的位置要与第 \(i\) 行剩下的格子所处的位置有相交的部分。设第 \(i-1\) 行剩下 \([l',r']\) 这部分格子。那么有 \([l,r]\cup[l',r']\ne\varnothing\)。状态转移方程:
初态:\(f_{0,1,m}=1\)。最终答案就是 \(\sum\limits_{l=1}^{m}\sum\limits_{r=l}^{m}f_{n,l,r}\)。
这样一来,我们就得到了一个时间复杂度 \(O(nm^4)\) 附带空间复杂度 \(O(nm^2)\) 的优秀算法,可以拿下 Unaccepted。
发现这个式子并不好优化。试着转化一下:将“状态有相交部分的总和”转化为“所有状态的总和减去没有相交部分的状态”。也就是
先来尝试一下优化空间。我们试着把 \(f_{i,l,r}\) 中 \(l\) 这一维去掉,也就是只保留右端点而枚举左端点。此时 \(f_{i,r}\) 表示第 \(i\) 行还剩下右端点为 \(r\) 的格子时整个网格仍然保持连通的概率。这样上面的状态转移方程中 \(\sum\limits_{l'=1}^{m}\sum\limits_{r'=l'}^{m}f_{i-1,l',r'}\) 这一部分的计算变成了 \(\sum\limits_{r'=1}^{m}f_{i-1,r'}\),\(\sum\limits_{l'=1}^{l-1}\sum\limits_{r'=l'}^{l-1}f_{i-1,l',r'}\) 这一部分的计算变成了 \(\sum\limits_{r'=1}^{l-1}f_{i-1,r'}\)。但是问题来了:\(\sum\limits_{l'=r+1}^{m}\sum\limits_{r'=l'}^{m}f_{i-1,l',r'}\) 怎么计算?
注意到本题有一个性质:\(f_{i,l,r}=f_{i,m-r+1,m-l+1}\)。由于两边对称,这条性质正确性显然。因此 \(\sum\limits_{l'=r+1}^{m}\sum\limits_{r'=l'}^{m}f_{i-1,l',r'}=\sum\limits_{l'=1}^{m-r}\sum\limits_{r'=l'}^{m-r}f_{i-1,l',r'}=\sum\limits_{r'=1}^{m-r}f_{i-1,r'}\)。
新的状态转移方程:
时间复杂度 \(O(nm^3)\),空间复杂度 \(O(nm)\),已经有很大进步了。
考虑继续优化。注意到 \(\sum\limits_{r'=1}^{m}f_{i-1,r'}-\sum\limits_{r'=1}^{l-1}f_{i-1,r'}-\sum\limits_{r'=1}^{m-r}f_{i-1,r'}\) 的值可以通过求 \(f_{i-1}\) 的前缀和求出。设 \(sum_j=\sum\limits_{k=1}^{j}f_{i-1,k}\),状态转移方程:
时间复杂度减少到了 \(O(nm^2)\),但还是不足以通过。
显然 \(i\) 和 \(r\) 是我们不得不去枚举的,因此尝试在 \(l\) 的枚举上做点文章,试一下能不能把 \(l\) 的枚举省去。先把状态转移方程中有关 \(l\) 和有关 \(r\) 的部分拆开:
这时我们惊奇地发现,\(\sum\limits_{l=1}^{r}P(l-1)\) 和 \(\sum\limits_{l=1}^{r}P(l-1)\times {sum}_{l-1}\) 这两个求和式中的元素都只和 \(l\) 有关。又可以使用前缀和优化了!
设 \(sump_i=\sum\limits_{j=0}^{i}P(i)\),\(ssum_i=\sum\limits_{j=1}^{i}P(i)\times sum_{i}\)。由于 \(sum_0=0\),所以可以不用把 \(P(0)\times sum_0\) 算进去。最终的状态转移方程:
初态:\(f_{0,m}=1\)。最终答案即为 \(\sum\limits_{r=1}^{m}f_{n,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;
}
Summary
在本题中,我们用一点点组合数学知识以及最简单最常用的前缀和的技巧就成功 AC。这再次印证了对简单算法灵活且熟练运用的重要性。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App