题目
题目大意
给你个非负整数数列(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
",sum%mo);
return 0;
}
总结
期望的线性性真是奇妙啊……