【CF708E】Student's Camp(动态规划)
- 初始有一张\((n+2)\times m\)的网格图。
- 共操作\(k\)轮,每轮除第一行和最后一行外每行的第一个格子和最后一个格子均有\(p=\frac ab\)的概率被删除。
- 求最终图连通的概率。
- \(n,m\le1500,k\le10^5\)
暴力动态规划
首先\(k\)相对\(n,m\)要大许多,肯定不会把它放到\(DP\)状态中。
因此我们只需要考虑最终状态,那就只需要设\(q_i\)表示在\(k\)轮删除操作中恰好进行\(i\)次的概率:
\[q_i=C_k^ip^i(1-p)^{k-i}
\]
由于前后的删除相对独立,因此设\(w_{l,r}\)表示最后被删得恰好只剩\([l,r]\)的概率,就有:
\[w_{l,r}=p_{l-1}\times p_{m-r}
\]
这样一来,如果我们设\(f_{i,l,r}\)表示第\(i\)行最终被删得只剩\([l,r]\)且前\(i\)行连通的概率,就只需考虑这一行与上一行是否连通:
\[f_{i,l,r}=w_{l,r}\times\sum_{[l,r]\cap[l',r']\not=\emptyset}f_{i-1,l',r'}
\]
两个区间有交,可以容斥用总方案数减去无交的方案数:
\[f_{i,l,r}=w_{l,r}\times(\sum_{l'\le r'}f_{i-1,l',r'}-\sum_{l'\le r'<l}f_{i-1,l',r'}-\sum_{r<l'\le r'}f_{i-1,l',r'})
\]
但这样的复杂度显然不太对劲,需要优化。
前缀和优化
首先我们发现\(f_{i,l,r}\)这状态数已经达到了三维,显然从这里就需要开始改造。
因此给它做两次前缀和:
\[g_{i,r}=\sum_{l=1}^rf_{i,l,r}\\
s_{i,j}=\sum_{r=1}^jg_{i,r}
\]
那么先前的转移式就可以写作:
\[f_{i,l,r}=w_{l,r}\times(s_{i-1,m}-s_{i-1,l-1}-s_{i-1,m-r})
\]
然后只考虑把\(g_{i,r}\)的转移式给写开:
\[\begin{align}
g_{i,r}&=\sum_{l=1}^rf_{i,l,r}\\
&=\sum_{l=1}^rw_{l,r}\times(s_{i-1,m}-s_{i-1,l-1}-s_{i-1,m-r})\\
&=q_{m-r}\times((\sum_{l=1}^rq_{l-1})\times(s_{i-1,m}-s_{i-1,m-r})-\sum_{l=1}^{r}q_{l-1}\times s_{i-1,l-1})
\end{align}
\]
于是只要在枚举\(r\)的同时维护好\(\sum_{l=1}^rq_{l-1}\)和\(\sum_{l=1}^{r}q_{l-1}\times s_{i-1,l-1}\)就可以直接转移了。
最终的答案显然就是\(s_{n,m}\)。
代码:\(O(nm)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1500
#define K 100000
#define X 1000000007
#define C(x,y) (1LL*Fac[x]*IFac[y]%X*IFac[(x)-(y)]%X)//组合数
using namespace std;
int n,m,k,p,q[K+5],f[2][N+5][N+5],g[N+5][N+5],s[N+5][N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int Fac[N+K],IFac[K+5];I void InitFac()//预处理阶乘和阶乘逆元
{
RI i;for(Fac[0]=i=1;i<=k;++i) Fac[i]=1LL*Fac[i-1]*i%X;
for(IFac[i=k]=QP(Fac[k],X-2);i;--i) IFac[i-1]=1LL*IFac[i]*i%X;
}
int main()
{
RI i,j,x,y;scanf("%d%d%d%d%d",&n,&m,&x,&y,&k),p=1LL*x*QP(y,X-2)%X,InitFac();
for(i=0;i<=k;++i) q[i]=1LL*C(k,i)*QP(p,i)%X*QP((1-p+X)%X,k-i)%X;//预处理k次中恰好成功i次的概率
for(g[0][m]=s[0][m]=i=1;i<=n;++i)//枚举每一行DP
{
for(x=y=j=0;j<=m;x=(x+q[j])%X,y=(1LL*q[j]*s[i-1][j]+y)%X,++j)//枚举j同时维护好和
g[i][j]=1LL*q[m-j]*(1LL*x*(s[i-1][m]-s[i-1][m-j]+X)%X-y+X)%X;//转移
for(s[i][0]=g[i][0],j=1;j<=m;++j) s[i][j]=(s[i][j-1]+g[i][j])%X;//对g前缀和求出s
}return printf("%d\n",s[n][m]),0;
}
待到再迷茫时回头望,所有脚印会发出光芒