[JZOJ6340] 【NOIP2019模拟2019.9.4】B

题目

题目大意

给你个非负整数数列\(a\),每次等概率选择大于零的\(a_i\),使其减\(1\)
\(a_1\)被减到\(0\)的时候期望经过多少次操作。


思考历程

对于这题的暴力做法,显然可以状态压缩吧……
然后我突然意识到,实际上我们将题目转化成以下模型:
\(n\)种颜色,第\(i\)种颜色的小球有\(a_i\)个。那么题目就变成了一个有重复元素的排列问题。
先将\(2\)\(n\)排列求出来,然后考虑将\(1\)随机插入。
枚举最后一个\(1\)出现的位置,然后在前面用组合数计算插入的方案数。
这个做法打出来之后一直没有调出来,也不知道是我数学功底不好还是这个方法本来就是错的……
当然,即便是对的,也不会是满分做法。


正解

根据期望的线性性,题目可以转化为:对于每个\(i>1\)\(a_1\)减到\(0\)的时候\(a_i\)期望取了多少个。然后将每个\(i\)的这东西加起来,再加上\(a_1\),就是答案。
于是我们就只需要考虑这个问题。
接着就是最骚的操作:我们只考虑\(a_1\)\(a_i\),其它被减掉对它们没有影响。它们有相等的概率被减\(1\),这个概率可以看做是\(\frac{1}{2}\)。于是相当于题目中\(n=2\)的情况。
现在考虑一个平面,一开始的坐标为\((a_1,a_i)\),每次随机地向下面或者左边走一格。
如果到达边界\((0,y)\),则会有\(a_i-y\)的贡献;如果到达边界\((x,0)\),则会有\(a_i\)的贡献。
对于边界\((0,y)\),设向下走了\(j\)步,则概率为\(\frac{C_{a_1+j-1}^{j}}{2^{a_1+j}}\)
对于边界\((x,0)\),既然它们的贡献都一样,不妨将上面的和用\(1\)减掉,就是它的概率。
接着就可以列出一个式子……
列出之后容易发现,\((a_1,a_i+1)\)的答案可以\(O(1)\)地从\((a_i,a_i)\)转移过来。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 500010
#define A 500010
#define mo 323232323
inline int input(){
	char ch=getchar();
	while (ch<'0' || '9'<ch)
		ch=getchar();
	int x=0;
	do{
		x=x*10+ch-'0';
		ch=getchar();
	}
	while ('0'<=ch && ch<='9');
	return x;
}
int n,a[N];
int fac[N+A],invf[N+A],inv2[N+A];
int ans1[A*3],ans2[A*3],ans[A*3];
inline int inv(int x){
	int res=1;
	for (int y=mo-2;y;y>>=1,x=(long long)x*x%mo)
		if (y&1)
			res=(long long)res*x%mo;
	return res;
}
inline int C(int m,int n){return (long long)fac[m]*invf[n]%mo*invf[m-n]%mo;}
int main(){
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	n=input();
	for (int i=1;i<=n;++i)
		a[i]=input();
	fac[0]=1,inv2[0]=1;
	for (int i=1;i<=A*2;++i){
		fac[i]=(long long)fac[i-1]*i%mo;
		inv2[i]=(long long)inv2[i-1]*2%mo;
	}
	for (int i=0;i<=A*2;++i){
		invf[i]=inv(fac[i]);
		inv2[i]=inv(inv2[i]);
	}
	ans1[0]=ans2[0]=0;
	for (int i=1;i<=A;++i){
		ans1[i]=(ans1[i-1]+(long long)C(a[1]+i-2,i-1)*(i-1)%mo*inv2[a[1]+i-1]%mo)%mo;
		ans2[i]=(ans2[i-1]+(long long)C(a[1]+i-2,i-1)*inv2[a[1]+i-1]%mo)%mo;
		ans[i]=(ans1[i]+(long long)i*(1-ans2[i]+mo)%mo)%mo;
	}
	long long sum=a[1];
	for (int i=2;i<=n;++i)
		sum+=ans[a[i]];
	printf("%lld\n",sum%mo);
	return 0;
}

总结

期望的线性性真是奇妙啊……

posted @ 2019-09-07 21:39  jz_597  阅读(180)  评论(0编辑  收藏  举报