[NOI2017]泳池

XXVIII.[NOI2017]泳池

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

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

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

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

注意,这里我们不考虑前j1行都是安全的概率——即,最终要乘上一个pi(j1)才是正确概率。

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

则我们应有

gi,j=kjfi,j×pi(kj)

后面那个pi(kj)是第j到第k1行全部安全的方案数。

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

gi,j=gi,j+1×pi+fi,j

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

fi,j=k=1i(lk+1fk1,l×pi(lj))×q×(lkfik,l×pi(lj))

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

g来替代,就得到了

fi,j=k=1i(gk1,j+1×pi)×q×gik,j

因为fi,j合法的前提,是i(j1)m,故实际上只有mlogmfi,j是合法的;对于每个fi,j,都O(m)地转移,就可以在O(m2logm)时间内完成。

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

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

我们再设ai表示一段长度为i且第1列内仅有最左端有一个危险格子的海滩的概率。则有ai=gi1,2×pi1

则我们有

hi=j=1m+1hijaj(i>m)

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

先别急,我们再来考虑一下im这一段的初始值:

hi=ai+1+j=1ihijaj(im)

然后就是模板了。这里多项式乘法和多项式取模都可选择直接暴力O(m2)处理,因为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;
} 

posted @   Troverld  阅读(78)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示