【概率期望】【NOIP模拟】切题

【题目描述】
Alice 出了 n 场比赛,第i场比赛有ai道题。

由于是提高组模拟赛,因此最多只会有三题。

Bob 要来切这些题。由于他艺高人胆大,所以他的切题方式比较特别。

Bob 从 1到 n这 n个整数中等概率均匀随机生成一个数i,作为他选择的比赛序号。如果第i场比赛还有 Bob 没切的题,Bob 会切掉一题,并说一声“我好菜啊”;否则他切不了题,但是由于他 AK 了点开的这场比赛,所以他同样会说“我好菜啊”。

每次进行完上述操作后,Bob 会从随机开始重新进行上述步骤,直到所有题被切完为止。

没多久,Bob 切完了所有的题,而 Alice 已经数不清楚他说了多少遍“我好菜啊”。

由于 Bob 的选择是随机的,可能无法还原真实情况,所以 Alice 只希望知道 Bob 期望说“我好菜啊”的次数。

期望就是指无数多次试验下的平均值。

也就是说,Alice 想知道,假设有无限次这样的试验,Bob 平均情况下经过几轮上述步骤能够最早切完所有题。

但是 Alice 不会概率期望,所以现在请你帮帮 Alice 吧。

由于 Bob 尤其喜欢切傅里叶变换的题,因此答案模 17680321(傅里叶的生日,是个质数)。

【输入】
第一行是一个整数n
接下来一行是n个整数ai
【输出】
输出一个答案,对17680321取模

【样例输入】
3
1 1 1
【样例输出】
8840166
(注:样例本来的答案是 11 2 \frac{11}{2} 211,本题限时1s,空间64M,N<=500)

【思路】

虽然是一个模板,但是这道题的一些细节问题也很有价值。

设计状态
首先考虑对于每个状态我们应该怎么描述。事实上,这里一共有九种信息需要描述(为了方便,我们记总共a道题还剩b道题的比赛为(a,b)):(3,3),(3,2),(3,1),(3,0),(2,2),(2,1),(2,0),(1,1),(1,0)。显然,这样空间肯定接受不了。所以我们考虑合并一些状态:转移的时候,我们发现下一步的状态与这个比赛的本身的题目数量是无关的。也就是说,(3,1),(2,1)(1,1)在转移状态时是完全等价的。于是我们就可以用四个信息描述一个状态:(3),(2),(1),(0)。可是空间依然开不下。这时候,我们发现,我们不需要全部的四个信息。我们只需要三个信息,第四个信息就可以通过总和为n求出来。换句话说,如果状态(x,y,a,b)合法,那么(x,y,a,b-1)就是不合法的状态,永远不可能需要它的信息。不妨舍去(0)这个信息,我们就可以定义状态 f i , j , k f_{i,j,k} fi,j,k表示从状态(i,j,k)到(0,0,0)的期望代价了。

设计方程
我们回顾一下期望最基本的公式
E ( x ) = ∑ i p i ∗ x i E(x)=\sum_{i}p_{i}*x_{i} E(x)=ipixi
其中 p i 和 x i p_{i}和x_{i} pixi表示随机变量x取 x i x_{i} xi的概率为 p i p_{i} pi。那么我们就考虑当前状态(i,j,k)可以转移到哪些状态。显然,它可以转移到(i-1,j,k)(i+1,j-1,k)(i,j-1,k+1)(i,j,k)这四个状态。那么我们可以写出方程:
f i , j , k = i n ∗ f i − 1 , j , k + j n ∗ f i + 1 , j − 1 , k + k n ∗ f i , j + 1 , k − 1 + n − i − j − k n ∗ f i , j , k f_{i,j,k}=\frac{i}{n}*f_{i-1,j,k}+\frac{j}{n}*f_{i+1,j-1,k}+\frac{k}{n}*f_{i,j+1,k-1}+\frac{n-i-j-k}{n}*f_{i,j,k} fi,j,k=nifi1,j,k+njfi+1,j1,k+nkfi,j+1,k1+nnijkfi,j,k
移项化简得:
f i , j , k = i i + j + k ∗ f i − 1 , j , k + j i + j + k ∗ f i + 1 , j − 1 , k + k i + j + k ∗ f i , j + 1 , k − 1 + n i + j + k f_{i,j,k}=\frac{i}{i+j+k}*f_{i-1,j,k}+\frac{j}{i+j+k}*f_{i+1,j-1,k}+\frac{k}{i+j+k}*f_{i,j+1,k-1}+\frac{n}{i+j+k} fi,j,k=i+j+kifi1,j,k+i+j+kjfi+1,j1,k+i+j+kkfi,j+1,k1+i+j+kn

具体实现
初始状态: f 0 , 0 , 0 f_{0,0,0} f0,0,0=0;

考虑枚举顺序:
我们发现当我们在更新 f i , j , k f_{i,j,k} fi,j,k时需要 f i − 1 , j , k f_{i-1,j,k} fi1,j,k的信息,所以i从小到大枚举。我们注意到我们在k时只需要k和k-1的信息。所以k从小到大枚举。而对于j,虽然我们既需要j+1也需要j-1,但是 f i , j + 1 , k − 1 f_{i,j+1,k-1} fi,j+1,k1在更新k-1时已经更新过了,所以k的循环只要放在j外面就好了。同理,对于i,虽然我们需要 f i + 1 , j − 1 , k f_{i+1,j-1,k} fi+1,j1,k的信息,但是我们在更新j-1时已经更新过了,所以j要放在i的外面。所以三个循环分别是k,j,i,三者均是从小到大枚举。

取模:由于答案是mod17680321意义下的答案。所以我们把除以(i+j+k)变成乘(i+j+k)的逆元。处理逆元:由于这道题模数比较小且是素数,同时我们只需要更新1-500的答案所以我们可以直接用费马小定理更新逆元(即 i n v ( a ) = a m o d − 2 inv(a)=a^{mod-2} inv(a)=amod2)。如果模数不是素数,那可能就需要用欧拉定理之类的方法了。

空间优化:这道题卡空间,所以我们需要把k这一维压掉,用滚动数组就行了。

时间优化:这道题有点卡常,所以我们需要剪去不必要的枚举。事实上,k和j不需要枚举到n,而对于确定的k和j,i也不一定需要枚举到n。

代码:

#include<bits/stdc++.h>
#define re register
using namespace std;
int n,m,a,b,c;
const int N=505;
const int mod=17680321;
inline int red()
{
    int data=0;int w=1; char ch=0;
    ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return data*w;
}
int num[4];
long long f[2][N][N];
int inv[N];
inline long long qpow(long long a,int b)
{
	long long ret=1;
	while(b)
	{
		if(b&1)ret*=a,ret%=mod;
		a*=a;a%=mod;
		b>>=1;
	}
	return ret;
}
inline void get_inv()
{
	for(long long re i=1;i<=500;i++)
		inv[i]=1ll*qpow(i,mod-2)%mod;
}
int main()
{
	n=red();get_inv();
	for(int re i=1;i<=n;i++)a=red(),num[a]++;
	for(int re k=0;k<=num[3];k++)
	{
		int pos=k&1;
		for(int re j=0;j<=num[2]+num[3]-k;j++)
		{
			for(int re i=0;i<=n-j-k;i++)
			{
				f[pos][j][i]=0;int all=i+j+k;
				if(!all)continue;
				if(i!=0)f[pos][j][i]+=1ll*f[pos][j][i-1]*i*inv[all]%mod;
				if(j!=0)f[pos][j][i]+=1ll*f[pos][j-1][i+1]*j*inv[all]%mod;
				if(k!=0)f[pos][j][i]+=1ll*f[pos^1][j+1][i]*k*inv[all]%mod;
				f[pos][j][i]+=1ll*n*inv[all]%mod;
				f[pos][j][i]%=mod;
			}
		}
	}
	printf("%lld",f[num[3]&1][num[2]][num[1]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值