[NOI2017]泳池

XXVIII.[NOI2017]泳池

常系数齐次线性递推的应用。

我们首先将问题转换为(面积小于等于\(K\)的方案数)减去(面积小于等于\(K-1\)的方案数)。

然后考虑两个东西分别DP。我们设当前考虑的是面积小于等于\(m\)的情况。

我们设\(f_{i,j}\)表示考虑一段长为\(i\)的沙滩,其中前\(j-1\)行都是安全的,而第\(j\)行至少有一个危险的地方时,只存在面积小于等于\(m\)的游泳池的概率。再设\(p\)为一个格子是安全的概率,\(q\)则为它是危险的概率。

注意,这里我们不考虑前\(j-1\)行都是安全的概率——即,最终要乘上一个\(p^{i(j-1)}\)才是正确概率。

我们考虑再设\(g_{i,j}\)表示前\(j-1\)行都是安全的,第\(j\)行往后可能出现危险格子,此时不存在面积大于\(m\)的游泳池的方案数。

则我们应有

\[g_{i,j}=\sum\limits_{k\geq j}f_{i,j}\times p^{i(k-j)} \]

后面那个\(p^{i(k-j)}\)是第\(j\)到第\(k-1\)行全部安全的方案数。

它可以通过后缀和的形式求出:

\[g_{i,j}=g_{i,j+1}\times p^i+f_{i,j} \]

下面我们考虑求出\(f\)。我们考虑枚举第一个危险格子出现在哪里。则我们就有

\[f_{i,j}=\sum\limits_{k=1}^i\Big(\sum\limits_{l\geq k+1}f_{k-1,l}\times p^{i(l-j)}\Big)\times q\times\Big(\sum\limits_{l\geq k}f_{i-k,l}\times p^{i(l-j)}\Big) \]

因为前一半中,前\(j\)行内必须没有任何东西;而后一半中,前\(j-1\)行必须没有任何东西;中间的一个位置还必须是危险的。所以我们可以得到上面的转移式。

\(g\)来替代,就得到了

\[f_{i,j}=\sum\limits_{k=1}^i(g_{k-1,j+1}\times p^i)\times q\times g_{i-k,j} \]

因为\(f_{i,j}\)合法的前提,是\(i(j-1)\leq m\),故实际上只有\(m\log m\)\(f_{i,j}\)是合法的;对于每个\(f_{i,j}\),都\(O(m)\)地转移,就可以在\(O(m^2\log m)\)时间内完成。

(实际上,观察到转移是个卷积,也可以用NTT优化到\(O(m\log^2n)\),但是没有必要)

我们考虑设\(h_i\)表示一个长度为\(i\)的海滩不存在大于\(m\)的游泳池的方案数。

我们再设\(a_i\)表示一段长度为\(i\)且第\(1\)列内仅有最左端有一个危险格子的海滩的概率。则有\(a_i=g_{i-1,2}\times p^{i-1}\)

则我们有

\[h_i=\sum\limits_{j=1}^{m+1}h_{i-j}a_j\qquad(i>m) \]

这是常系数齐次线性递推的形式!!!

先别急,我们再来考虑一下\(i\leq m\)这一段的初始值:

\[h_i=a_{i+1}+\sum\limits_{j=1}^{i}h_{i-j}a_j\qquad(i\leq m) \]

然后就是模板了。这里多项式乘法和多项式取模都可选择直接暴力\(O(m^2)\)处理,因为\(m\)只有\(1000\)

#include<bits/stdc++.h>
using namespace std;
const int N=1<<20;
const int mod=998244353;
int ksm(int x,int y){
	int z=1;
	for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;
	return z;
}
int n,K,p,q,m,a[1010],tmp[2010],pov[1010];
void modulo(int *f){
	for(int i=2*m-2;i>=m;i--){
		int delta=f[i];
		for(int j=0;j<=m;j++)(f[i-j]+=mod-1ll*delta*a[m-j]%mod)%=mod;
	}
}
void times(int *f,int *g){
	for(int i=0;i<=2*m-2;i++)tmp[i]=0;
	for(int i=0;i<m;i++)for(int j=0;j<m;j++)(tmp[i+j]+=1ll*f[i]*g[j]%mod)%=mod;
	for(int i=0;i<=2*m-2;i++)f[i]=tmp[i];
	modulo(f);
}
int f[1010][1010],g[1010][1010],c[2010],d[2010],h[1010];
void KSM(){
	memset(c,0,sizeof(c)),memset(d,0,sizeof(d));
	d[0]=1,c[1]=1;
	for(int y=n;y;y>>=1,times(c,c))if(y&1)times(d,c);
}
int solve(int ip){
	m=ip;
	memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
	for(int i=2;i<=m+2;i++)g[0][i]=1;
	for(int i=1;i<=m;i++)for(int j=m/i+1;j>=2;j--){
		for(int k=1;k<=i;k++)(f[i][j]+=1ll*g[k-1][j+1]*pov[k-1]%mod*q%mod*g[i-k][j]%mod)%=mod;
		g[i][j]=(1ll*g[i][j+1]*pov[i]+f[i][j])%mod;
	}
	memset(a,0,sizeof(a));
	for(int i=0;i<=m;i++)a[i+1]=1ll*q*g[i][2]%mod*pov[i]%mod;
	m++;
	memset(h,0,sizeof(h));
	h[0]=1;
	for(int i=1;i<m;i++){h[i]=1ll*g[i][2]*pov[i]%mod;for(int j=1;j<=i;j++)(h[i]+=1ll*h[i-j]*a[j]%mod)%=mod;}
	reverse(a,a+m+1),a[m]=1;
	for(int i=0;i<m;i++)a[i]=(mod-a[i])%mod;
	KSM();
	int res=0;
	for(int i=0;i<m;i++)(res+=1ll*h[i]*d[i]%mod)%=mod;
	return res;
}
int main(){
	int X,Y;
	scanf("%d%d%d%d",&n,&K,&X,&Y),p=1ll*X*ksm(Y,mod-2)%mod,q=(mod+1-p)%mod;
	pov[0]=1;
	for(int i=1;i<=K;i++)pov[i]=1ll*pov[i-1]*p%mod;
	X=solve(K);
	Y=solve(K-1);
	printf("%d\n",(X-Y+mod)%mod);
	return 0;
} 

原文地址:https://www.cnblogs.com/Troverld/p/14608077.html