(期望DP)[NOI2019]斗主地

本文探讨了牌堆洗牌过程中牌位变化的期望值计算问题,通过建立数学模型,利用动态规划思想推导出计算公式,并实现了两种不同复杂度的算法方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

蒟蒻同步赛选手

考试的时候手推式子推了半天,结果只写个个 O(mn2)\ O(mn^{2}) O(mn2),水了 30\ 30 30

我们设 fi,j\ f_{i,j} fi,j是经过 i\ i i次洗牌第 j\ j j张牌的期望(从下往上), fai,j,fbi,j\ fa_{i,j},fb_{i,j} fai,j,fbi,j是两堆牌经过 i\ i i次洗牌的 j\ j j张牌的期望,我们有:

 A,B\ A,B A,B是两堆牌的高度。

fi,j=∑k=1j(j−1k−1)Ak‾Bj−k‾(fai−1,k)+(j−1k−1)Bk‾Aj−k‾(fbi−1,k)nj‾f_{i,j}=\sum_{k=1}^{j} \frac{\binom{j-1}{k-1}A^{\underline{k}}B^{\underline{j-k}}(fa_{i-1,k})+\binom{j-1}{k-1}B^{\underline{k}}A^{\underline{j-k}}(fb_{i-1,k})}{n^{\underline{j}}}fi,j=k=1jnj(k1j1)AkBjk(fai1,k)+(k1j1)BkAjk(fbi1,k)

为甚会得到这个式子呢?

我们有以下分析:

先看这个图:

 c\ c c的高度为 A\ A A d\ d d的高度为 B\ B B

我们逐个分析:

先不考虑其顺序。

第一张牌:

  • 如果我们取 c1\ c1 c1,显然 c1\ c1 c1的贡献为 Anc1\ \frac{A}{n}c1 nAc1
  • 如果我们取 d1\ d1 d1,显然 d1\ d1 d1的贡献为 Bnd1\ \frac{B}{n}d1 nBd1

第二张牌

  • 如果我们取 c1\ c1 c1,贡献为 ABn(n−1)c1\ \frac{AB}{n(n-1)}c1 n(n1)ABc1
  • 如果我们取 d1\ d1 d1,贡献为 ABn(n−1)d1\ \frac{AB}{n(n-1)}d1 n(n1)ABd1
  • 如果我们取 c2\ c2 c2,贡献为 A(A−1)n(n−1)c2\ \frac{A(A-1)}{n(n-1)}c2 n(n1)A(A1)c2
  • 如果我们取 d2\ d2 d2,贡献为 B(B−1)n(n−1)d2\ \frac{B(B-1)}{n(n-1)}d2 n(n1)B(B1)d2

⋯\cdots

显然的,当我们第 j\ j j张选了 ck\ ck ck时,分子上肯定有 nnum‾\ n^{\underline{num}} nnum,分子上肯定有 Ak‾\ A^{\underline{k}} Ak Bj−k‾\ B^{\underline{j-k}} Bjk。对于 dk\ dk dk,我们有同样的分析。

那么考虑顺序的话是很么呢?显然是一个组合数 (j−1k−1)\ \binom{j-1}{k-1} (k1j1)。这个请读者自己分析。

致辞我们可以得到代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const long long mod=998244353;
//char buf[1<<20],*fs,*ft;
//inline char getc()
//{return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))? 0 : *fs++;}
inline long long read()
{long long s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
//char buf1[1<<21],a1[20];int p11,p12=-1;
//inline void flush()
//{fwrite (buf1,1,p12+1,stdout),p12=-1;}
//inline void print(int x)
//{if(p12>1<<20) flush();if(x<0) buf1[++p12]=45,x=-x;
//do{a1[++p11]=x%10+48;}while(x/=10);
//do{buf1[++p12]=a1[p11];}while(--p11);buf1[++p12]='\n';}
/*
g the two line
f the c
frac n!
invf the inv of frac
a A
*/
long long n,m,a[100100],f[2][100100],frac[100100],invf[100100],g[1100][1100],type;
long long fa[100100],fb[100100];
inline long long poww(long long a,long long b)
{
	long long res=1ll;
	a%=mod;
	while(b)
	{
		if(b&1)
		{
			res*=a;
			res%=mod;
		}
		a*=a;
		a%=mod;
		b>>=1;
	}
	return res;
}
inline long long sum(int aa,int bb,int num,int k)
{
	long long ans=invf[n]*frac[n-num-1];
	ans%=mod;
	ans*=g[k-1][num-(k-1)];
	ans%=mod;
	int aused1,bused1,aused2,bused2;
	//ues in a
	aused1=k-1ll;
	bused1=num-(k-1ll);
	//use in b
	aused2=num-(k-1ll);
	bused2=k-1ll;
	long long suma=frac[aa]*invf[aa-aused1];
	suma%=mod;
	if((aused1<aa)&&(bused1<=bb))
	{
	suma*=frac[bb];
	suma%=mod;
	suma*=invf[bb-bused1];
	suma%=mod;
	suma*=aa-aused1;
	suma%=mod;
	suma*=fa[k];
	suma%=mod;
	}
	else suma=0;
	
	long long sumb=frac[bb]*invf[bb-bused2];
	sumb%=mod;
	if((bused2<bb)&&(aused2<=aa))
	{
	sumb*=frac[aa];
	sumb%=mod;
	sumb*=invf[aa-aused2];
	sumb%=mod;
	sumb*=bb-bused2;
	sumb%=mod;
	sumb*=fb[k];
	sumb%=mod;
	}
	else sumb=0;
	
	long long sum=suma+sumb;
	sum%=mod;
	ans*=sum%mod;
	ans%=mod;
	return ans;
}
/*
have got num cards and the next is the kth
*/
signed main()
{
//	freopen("landlords.in","r",stdin);
//	freopen("landlords.out","w",stdout);
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g)); 
	n=read();
	m=read();
	type=read();
	int i=1,j=1,k=1;
	while(i<=m)
	{
		a[i]=read();
		++i;
	}
	g[0][0]=1;
	i=0;
	while(i<=n)
	{
		j=0;
		while(j<=n)
		{
			if(i) g[i][j]+=g[i-1][j];
			g[i][j]%=mod;
			if(j) g[i][j]+=g[i][j-1];
			g[i][j]%=mod;
			++j;
		}
		++i;
	}
	frac[0]=1;
	i=1;
	while(i<=n)
	{
		frac[i]=frac[i-1]*i;
		frac[i]%=mod;
		++i;
	}
	i=0;
	while(i<=n)
	{
		invf[i]=poww(frac[i],mod-2);
		invf[i]%=mod;
		++i;
	}
	i=1;
	while(i<=n)
	{
		f[0][n-i+1]=poww(i,type);
		f[0][n-i+1]%=mod;
		++i;
	}
	int cur=0;
	i=1;
	while(i<=m)
	{
		cur^=1;
		int aa=0,bb=0;
		j=n-a[i]+1;
		memset(f[cur],0,sizeof(f[cur]));
		memset(fa,0,sizeof(fa));
		memset(fb,0,sizeof(fb));
		while(j<=n)
		{
			fa[++aa]=f[cur^1][j];
			++j;
		}
		j=1;
		while(j<=n-a[i])
		{
			fb[++bb]=f[cur^1][j];
			++j;
		}
		j=1;
		while(j<=n)
		{
			k=1;
			while(k<=min(j,max(aa,bb)))
			{
				f[cur][j]+=sum(aa,bb,j-1,k);
				f[cur][j]%=mod;
				++k;
			}
			++j;
		}
		++i;
	}
	int qq=read();
	while(qq--)
	{
		int cc=read();
//		while(f[cur][n-cc+1]<0) f[cur][n-cc+1]+=mod;
		printf("%lld\n",f[cur][n-cc+1]);
	}
	return 0;
}

当然只有三十分, O(mn2)\ O(mn^{2}) O(mn2)的复杂度是很不优秀的。

我们经过打表证明发现在 f(i)=i\ f(i)=i f(i)=i时答案是一个等差数列,那么我们就只需要记录前两项了。

证明:

我们思考一下这个过程:我们发现任意时刻我们的牌堆都是上面的牌没有下面的牌大的(不取膜)。我们挖掘这个东西的性质,发现它实际上是基数排序的逆过程。而且原序列的所有情况概率是相同的。

同理可得 f(i)=i2\ f(i)=i^{2} f(i)=i2的时候是二次函数

实际上我们不需要分情况讨论,一次函数本身就是特殊的二次函数。

此处我用拉格朗日插值就二次函数通项,大佬可以用二阶差分啥的。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const long long mod=998244353;
//char buf[1<<20],*fs,*ft;
//inline char getc()
//{return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))? 0 : *fs++;}
inline long long read()
{long long s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;}
//char buf1[1<<21],a1[20];int p11,p12=-1;
//inline void flush()
//{fwrite (buf1,1,p12+1,stdout),p12=-1;}
//inline void print(int x)
//{if(p12>1<<20) flush();if(x<0) buf1[++p12]=45,x=-x;
//do{a1[++p11]=x%10+48;}while(x/=10);
//do{buf1[++p12]=a1[p11];}while(--p11);buf1[++p12]='\n';}
/*
g the two line
f the c
frac n!
invf the inv of frac
a A
*/
long long n,m,a[500500],f[2][5],frac[10001000],invf[10001000],type;
long long fa[5],fb[5];
long long g(long long i,long long j)
{
	long long ans=frac[i+j];
	ans*=invf[i];
	ans%=mod;
	ans*=invf[j];
	ans%=mod;
	return ans;
}
inline long long poww(long long a,long long b)
{
	long long res=1ll;
	a%=mod;
	while(b)
	{
		if(b&1)
		{
			res*=a;
			res%=mod;
		}
		a*=a;
		a%=mod;
		b>>=1;
	}
	return res;
}
inline long long sum(int aa,int bb,int num,int k)
{
	long long ans=invf[n]*frac[n-num-1];
	ans%=mod;
	ans*=g(k-1,num-(k-1));
	ans%=mod;
	int aused1,bused1,aused2,bused2;
	//ues in a
	aused1=k-1ll;
	bused1=num-(k-1ll);
	//use in b
	aused2=num-(k-1ll);
	bused2=k-1ll;
	long long suma=frac[aa]*invf[aa-aused1];
	suma%=mod;
	if((aused1<aa)&&(bused1<=bb))
	{
	suma*=frac[bb];
	suma%=mod;
	suma*=invf[bb-bused1];
	suma%=mod;
	suma*=aa-aused1;
	suma%=mod;
	suma*=fa[k];
	suma%=mod;
	}
	else suma=0;
	
	long long sumb=frac[bb]*invf[bb-bused2];
	sumb%=mod;
	if((bused2<bb)&&(aused2<=aa))
	{
	sumb*=frac[aa];
	sumb%=mod;
	sumb*=invf[aa-aused2];
	sumb%=mod;
	sumb*=bb-bused2;
	sumb%=mod;
	sumb*=fb[k];
	sumb%=mod;
	}
	else sumb=0;
	
	long long sum=suma+sumb;
	sum%=mod;
	ans*=sum%mod;
	ans%=mod;
	return ans;
}
/*
have got num cards and the next is the kth
*/
inline void getabc(long long x0,long long y0,long long x1,long long y1,long long x2,long long y2,long long &aa,long long &bb,long long &cc)
{
	aa=0;
	bb=0;
	cc=0;
	long long sum=0;
	//0a 0b
	sum=(x0-x1)*(x0-x2);
	sum%=mod;
	sum=poww(sum,mod-2);
	sum*=y0;
	sum%=mod;
	aa+=sum;
	aa%=mod;
	sum*=(x1+x2);
	sum%=mod;
	bb+=sum;
	bb%=mod;
	//1a 1b
	sum=(x1-x0)*(x1-x2);
	sum%=mod;
	sum=poww(sum,mod-2);
	sum*=y1;
	sum%=mod;
	aa+=sum;
	aa%=mod;
	sum*=(x0+x2);
	sum%=mod;
	bb+=sum;
	bb%=mod;
	//2a 2b
	sum=(x2-x0)*(x2-x1);
	sum%=mod;
	sum=poww(sum,mod-2);
	sum*=y2;
	sum%=mod;
	aa+=sum;
	aa%=mod;
	sum*=(x0+x1);
	sum%=mod;
	bb+=sum;
	bb%=mod;
	
	bb*=-1;
	bb%=mod;
	if(bb<0) bb+=mod;
	//0c
	sum=(x0-x1)*(x0-x2);
	sum%=mod;
	sum=poww(sum,mod-2);
	sum*=y0;
	sum%=mod;
	sum*=x1;
	sum%=mod;
	sum*=x2;
	sum%=mod;
	cc+=sum;
	cc%=mod;
	//1c
	sum=(x1-x0)*(x1-x2);
	sum%=mod;
	sum=poww(sum,mod-2);
	sum*=y1;
	sum%=mod;
	sum*=x0;
	sum%=mod;
	sum*=x2;
	sum%=mod;
	cc+=sum;
	cc%=mod;
	//2c
	sum=(x2-x0)*(x2-x1);
	sum%=mod;
	sum=poww(sum,mod-2);
	sum*=y2;
	sum%=mod;
	sum*=x0;
	sum%=mod;
	sum*=x1;
	sum%=mod;
	cc+=sum;
	cc%=mod;
}
signed main()
{
//	freopen("landlords2.in","r",stdin);
//	freopen("landlords2.out","w",stdout);
	memset(f,0,sizeof(f)); 
	n=read();
	m=read();
	type=read();
	int i=1,j=1,k=1;
	while(i<=m)
	{
		a[i]=read();
		++i;
	}
	frac[0]=1;
	i=1;
	while(i<=n)
	{
		frac[i]=frac[i-1]*i;
		frac[i]%=mod;
		++i;
	}
	invf[n]=poww(frac[n],mod-2);
	i=n;
	while(i>=1)
	{
		invf[i-1]=invf[i]*i;
		invf[i-1]%=mod;
		--i;
	}
	i=n;
	while(i>=n-3)
	{
		f[0][n-i+1]=poww(i,type);
		f[0][n-i+1]%=mod;
		--i;
	}
	int cur=0;
	i=1;
	while(i<=m)
	{
		cur^=1;
		int aa=0,bb=0;
		memset(f[cur],0,sizeof(f[cur]));
		memset(fa,0,sizeof(fa));
		memset(fb,0,sizeof(fb));
		j=1;
		while(j<=3)
		{
			fb[++bb]=f[cur^1][j];
			++j;
		}
		bb=n-a[i];
		aa=a[i];
		long long ax,bx,cx;
		getabc(1,fb[1],2,fb[2],3,fb[3],ax,bx,cx);
		int x=n-a[i]+1;
		j=1;
		while(j<=3)
		{
			fa[j]=ax*x;
			fa[j]%=mod;
			fa[j]*=x;
			fa[j]%=mod;
			fa[j]+=(bx*x)%mod;
			fa[j]%=mod;
			fa[j]+=cx;
			fa[j]%=mod;
			++x;
			++j;
		}
		j=1;
		while(j<=3)
		{
			k=1;
			while(k<=3)
			{
				f[cur][j]+=sum(aa,bb,j-1,k);
				f[cur][j]%=mod;
				++k;
			}
			++j;
		}
		++i;
	}
	long long aaa,bbb,ccc;
	getabc(1,f[cur][1],2,f[cur][2],3,f[cur][3],aaa,bbb,ccc);
	int qq=read();
	while(qq--)
	{
		int cc=read();
		cc=n-cc+1;
		int ans=0;
		ans=aaa*cc;
		ans%=mod;
		ans*=cc;
		ans%=mod;
		ans+=(cc*bbb)%mod;
		ans%=mod;
		ans+=ccc;
		ans%=mod;
		if(ans<0) ans+=mod;
		printf("%lld\n",ans);
	}
	return 0;
}

 为京阿尼祈福\ \color{white}\text{为京阿尼祈福} 为京阿尼祈福

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值