浅谈数学期望
离散期望
我不会连续期望,所以只讲离散期望。
式子
\(p_k\) 为概率,\(x_k\) 为要求期望值的值,\(E(X)\) 为事件 \(X\) 的期望。
\(E(X)=\sum\limits_k p_k\times x_k\)
性质
-
设 \(X\) 是随机变量,\(C\) 是常数,则 \(E(CX)=C\times E(X)\)。
-
设 \(X,Y\) 是任意两个随机变量,则有 \(E(X+Y)=E(X)+E(Y)\)。
-
设 \(X,Y\) 是相互独立的随机变量,则有 \(E(XY)=E(X)\times E(Y)\)。
-
设 \(C\) 为常数,则 \(E(C)=C\)。
题目
UVA10288 优惠券 Coupons
设 \(f_i\) 为有 \(i\) 个图案,获得下一个不同图案的期望次数,那么 \(f_i= \frac{n-i}{n}+\frac{i}{n}(f_i+1)\),分别加号两边分别表示得到了不同的和得到了相同的。
化简,有 \(f_i=\frac{n}{n-i}\)
所以 \(Ans=\sum\limits_{i=1}^n f_i\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=35;
int n;
int fz=0,fm=1;
void RFCD(int &fz1,int &fm1,int fz2,int fm2){
int resfz=fz1*fm2+fz2*fm1,resfm=fm1*fm2,G=__gcd(resfz,resfm);
fz1=resfz/G,fm1=resfm/G;
}
int getlen(int x){
int res=0;
do x/=10,++res;while(x);
return res;
}
signed main(){
while(scanf("%lld",&n)==1){
fz=0,fm=1;
for(int i=1;i<=n;++i) RFCD(fz,fm,1,i);
fz*=n;
int G=__gcd(fz,fm);
fz/=G,fm/=G;
if(fm==1) printf("%lld\n",fz);
else{
int cnt=fz/fm,len=getlen(cnt);
fz=fz-fz/fm*fm;
int len2=max(getlen(fz),getlen(fm));
for(int i=1;i<=len+1;++i) putchar(' ');
printf("%lld\n",fz);
printf("%lld",cnt);
putchar(' ');
while(len2) putchar('-'),--len2;
putchar('\n');
for(int i=1;i<=len+1;++i) putchar(' ');
printf("%lld\n",fm);
}
}
return 0;
}
Luogu P1654 OSU!
因为要连续成功,所以我们计算当前成功的贡献,并累加进答案里面,每次贡献为 \(1\)。
如果贡献是 \(x^1\),设 \(a_i\) 为前 \(i\) 次操作全为成功的得分期望,则有 \(a_i=p_i\cdot (a_{i-1}+1)\)
如果贡献是 \(x^2\),设 \(b_i\) 为前 \(i\) 次操作全为成功的得分期望,则有 \(b_i=p_i\cdot (b_{i-1}+2a_{i-1}+1)\)
所以设 \(f_i\) 为前 \(i\) 次操作的得分期望,则有 \(f_i=f_{i-1}+p_i\cdot (3a_{i-1}+3b_{i-1}+1)\)
如何理解?我们也是考虑算每个位置对答案的贡献,由于贡献是均等的,所以不用额外考虑别的,当前位置有 \(p_i\) 的概率成功,如果成功则对答案有 \(3a_{i-1}+3b_{i-1}+1\) 贡献,如果不成功就没有贡献,求的是前缀,所以我们还要加上前 \(i-1\) 的期望。
为什么是这个贡献?可以证明 \(\sum\limits_{i=0}^{k-1} 3k^2+3k+1=k^3\)。
至于有些题解中的
\(f[i]=(f[i−1]+3×b[i−1]+3×a[i−1]+1)×p[i]+f[i−1]×(1−p[i])=f[i−1]+(3×b[i−1]+3×a[i−1]+1)×p[i]\)
我认为是错误的,因为无法解释这个式子的含义(至写稿时没有看到能解释清楚这个式子的博客),虽然化简后是一致的,但好比我尝试用二项式定理证明 \(Cayley\) 定理一样,式子是一致的,但是无法解释式子的含义。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
double p[N],a[N],b[N],f[N];
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>p[i];
a[i]=(a[i-1]+1)*p[i];
b[i]=(b[i-1]+2*a[i-1]+1)*p[i];
f[i]=(3*b[i-1]+3*a[i-1]+1)*p[i]+f[i-1];
}
printf("%.1lf",f[n]);
return 0;
}
忽略 cin
和 printf
的混用。
CF235B Let's Play Osu!
双倍经验,类似于上面的做法,只需推到平方就好了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
double f[N],a[N],p[N];
int main(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>p[i];
f[i]=(2*a[i-1]+1)*p[i]+f[i-1];
a[i]=(a[i-1]+1)*p[i];
}
printf("%.7lf",f[n]);
return 0;
}
P1365 WJMZBMR打osu! / Easy
带点转移了,算是期望dp了吧。
类比上两题,设 \(f_i\) 为前 \(i\) 位得分平方的期望,\(a_i\) 为前 \(i\) 位都为 o
的得分的一次方的期望。
对于 o
,有 \(a_i=a_{i-1}+1,f_i=f_{i-1}+2a_{i-1}+1\)
对于 x
,有 \(a_i=0,f_i=f_{i-1}\)
对于 ?
,有 \(a_i=p_i(a_{i-1}+1),f_i=\frac{1}{2}f_{i-1}+\frac{f_{i-1}+2a_{i-1}+1}{2}=\frac{2f_{i-1}+2a_{i-1}+1}{2}\)
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n;
double f[N],a[N];
char s[N];
int main(){
cin>>n>>s;
for(int i=0;i<n;++i){
if(s[i]=='x') f[i]=f[i-1];
else if(s[i]=='o') f[i]=f[i-1]+2*a[i-1]+1,a[i]=a[i-1]+1;
else f[i]=(2.0*f[i-1]+2*a[i-1]+1)/2,a[i]=(a[i-1]+1)*1.0/2;
}
printf("%.4lf",f[n-1]);
return 0;
}
Luogu P4550 收集邮票
得分其实是等差数列,对于买了 \(x\) 次,花费为 \(\frac{(1+x)x}{2}=\frac{x^2+x}{2}\)。
设 \(a_i\) 为找到 \(i\) 个数之后还要的期望次数,\(f_i\) 为找到 \(i\) 个数之后还要的期望平方次数,答案是 \(\frac{a_0+f_0}{2}\)。
参考之前的优惠券那题,有 \(a_i=\frac{i}{n}(a_i+1)+\frac{n-i}{n}(a_{i+1}+1)\),
化简得:\(a_i=a_{i+1}+\frac{n}{n-i}\)
平方类似,可以得到 \(f_i=\frac{i}{n}(f_i+2a_i+1)+\frac{n-i}{n}(f_{i+1}+2a_{i+1}+1)\),
化简得:\(f_i=\frac{i}{n-i}(2a_{i-1}+1)+f_{i+1}+2a_{i+1}+1\)
边界条件:\(f_n=a_n=0\)。
#include<bits/stdc++.h>
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=10005;
int n;
double f[N],a[N];
int main(){
n=read<int>();
a[n]=f[n]=0;
for(int i=n-1;i>=0;--i){
a[i]=a[i+1]+n*1.0/(n-i);
f[i]=f[i+1]+2.0*i/(n-i)*a[i]+2*a[i+1]+n*1.0/(n-i);
}
printf("%.2lf",(f[0]+a[0])/2);
return 0;
}
Luogu P1850 [NOIP2016 提高组] 换教室
先用 \(\text{floyd}\) 处理出最短路,
设 \(dp_{i,j,0/1}\) 为第 \(i\) 个时间段,一共申请了 \(j\) 次,当前是否申请过的期望路径长度。
对于 \(dp_{i,j,0}=min\begin{cases} dp_{i-1,j,0}+dis_{c_{i-1},d_i} \\dp_{i-1,j,1}+dis_{d_{i-1},c_i}\cdot p_{i-1}+dis_{c_{i-1},c_i}\cdot (1-p_{i-1}) \end{cases}\)
对于 \(dp_{i,j,1}=min\begin{cases} dp_{i-1,j-1,1}+dis_{d_{i-1},d_i}\cdot p_i\cdot p_{i-1}+dis_{d_{i-1},c_i}\cdot p_{i-1}\cdot (1-p_i)+dis_{c_{i-1},d_i}\cdot (1-p_{i-1})\cdot p_i+dis_{c_{i-1},c{i}}\cdot (1-p_{i-1})\cdot (1-p_i)\\ dp_{i-1,j-1,0}+dis_{c_{i-1},d_i}\cdot p_i+dis_{c_{i-1},c_i}\cdot (1-p_i)\end{cases}\)
就是枚举他会在哪个教室转移即可。
#include<bits/stdc++.h>
using namespace std;
const int N=2005;
const double INF=1e7;
int n,m,v,e;
int c[N],d[N];
double p[N],ans=INF;
int g[N][N];
double dp[N][N][2];
void floyd(){
for(int k=1;k<=v;++k)
for(int i=1;i<=v;++i)
for(int j=1;j<=v;++j)
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
int main(){
scanf("%d%d%d%d",&n,&m,&v,&e);
for(int i=1;i<=n;++i) scanf("%d",&c[i]);
for(int i=1;i<=n;++i) scanf("%d",&d[i]);
for(int i=1;i<=n;++i) scanf("%lf",&p[i]);
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++) g[i][j]=g[j][i]=INF;
for(int i=1;i<=e;++i){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
g[y][x]=min(g[y][x],z),g[x][y]=g[y][x];
}
floyd();
for(int i=1;i<=v;++i) g[i][i]=g[i][0]=g[0][i]=0;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++) dp[i][j][0]=dp[i][j][1]=INF;
dp[1][0][0]=dp[1][1][1]=0;
for(int i=2;i<=n;++i){
for(int j=0;j<=min(i,m);++j){
dp[i][j][0]=min(dp[i-1][j][0]+g[c[i-1]][c[i]],dp[i-1][j][1]+g[d[i-1]][c[i]]*p[i-1]+g[c[i-1]][c[i]]*(1-p[i-1]));
if(j>0) dp[i][j][1]=min(dp[i-1][j-1][1]+g[d[i-1]][d[i]]*p[i]*p[i-1]+g[d[i-1]][c[i]]*p[i-1]*(1-p[i])+g[c[i-1]][d[i]]*(1-p[i-1])*p[i]+g[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i]),dp[i-1][j-1][0]+g[c[i-1]][d[i]]*p[i]+g[c[i-1]][c[i]]*(1-p[i]));
}
}
for(int i=0;i<=m;++i) ans=min(dp[n][i][0],min(dp[n][i][1],ans));
printf("%.2lf",ans);
return 0;
}
Luogu P3830 [SHOI2012]随机树
对于第 \(1\) 问,每次扩展一个叶子,设其原深度为 \(x\),相当于总深度增加了 \(2(x+1)−x=x+2\),
设对于 \(n\) 个叶子的树,平均叶子深度若为 \(f_n\),则总深度为 \(f_n\times n\),
在这个树中选择一个叶子展开,平均叶子增加深度为 \(f_n+2\),
所以 \(f_{n+1}=\frac{(f_n\times n+f_n+2)}{n+1}=f_n+\frac{2}{n+1}\)。
对于第 \(2\) 问,设 \(dp_{i,j}\) 为有 \(i\) 个叶子结点的树,树深大于等于 \(j\) 的概率。
每个子树内的情况是独立的,所以我们可以从每个子树的情况转移过来。
设左子树数量为 \(k\),那么右子树数量为 \(i-k\),确定了左子树的个数,右子树的个数也随之确定了,所以我们需要先乘上一个 \(P_{i,k}\) 表示一共有 \(i\) 个叶子结点,左子树有 \(k\) 个的概率。考虑如果左右子树单独提出来深度大于等于 \(j-1\),那么整个树的深度就会大于等于 \(j\),但是如果左右子树的深度都为 \(j-1\) 时会算重,减去一个他们同时深度为 \(j-1\) 的概率即可,所以我们有:
\(dp_{i,j}=\sum\limits_{k=1}^{i-1}P_{i,k}\cdot (dp_{k,j-1}+dp_{i-k,j-1}-dp_{k,j-1}\cdot dp_{i-k,j-1})\)
考虑一棵左子树有 \(k\) 个叶子结点,右子树有 \(i-k\) 个叶子结点的树是怎样生成的。
如果把所有操作写成一个序列,\(L\) 表示在左子树操作, \(R\) 表示在右子树操作。由于最开始一定是根分裂,于是左右子树分别还有 \(k-1,i-k-1\) 个叶子结点需要生成。那最后看起来可能会是这样。
它是一个有 \(k-1\) 个 \(L\) 和 \(i-k-1\) 个 \(R\) 的序列,也就是说,对于任何一对左右的操作序列,他们能组成 \(\binom{i-k-1+k-1}{k-1}=\frac{(i-2)!}{(k-1)!(i-k-1)!}\) 种不同的操作序列。
考虑生成一棵有 \(k\) 个叶子的树的方案数,也即有多少种操作序列。首先由 \(1\) 到 \(2\) 个叶子时只有一个选择,然后由 \(2\) 到 \(3\) 有两个选择......由 \(k-1\) 到 \(k\) 有 \(k-1\) 种选择。所以有 \((k-1)!\) 种方案。
同理,生成一棵有 \(i-k\) 个叶子的树的方案数为 \((i-k-1)!\)。
构造出这个序列的方案数就是将这些乘起来,即 \(\frac{(i-2)!}{(k-1)!(i-k-1)!}\cdot (k-1)!\cdot (i-k-1)!=(i-2)!\)
又因为生成一颗有 \(i\) 个叶子结点的树的方案数为 \((i-1)!\)
所以左子树有 \(k\) 个叶子结点的概率为 \(\frac{(i-2)!}{(i-1)!}=\frac{1}{i-1}\),与总叶子结点数无关,所以式子中的 \(P_{i,k}=i-1\)。
#include<bits/stdc++.h>
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=105;
int p,n;
double f[N],dp[N][N],ans;
int main(){
p=read<int>(),n=read<int>();
if(p==1){
for(int i=2;i<=n;++i) f[i]=f[i-1]+2.0/i;
printf("%.6lf",f[n]);
}
else{
for(int i=0;i<=n;++i) dp[i][0]=1; dp[2][1]=1;
for(int i=3;i<=n;++i)
for(int j=1;j<=i-1;++j){
for(int k=1;k<i;++k)
dp[i][j]+=dp[k][j-1]+dp[i-k][j-1]-dp[k][j-1]*dp[i-k][j-1];
dp[i][j]/=i-1;
}
for(int i=2;i<=n;++i) ans+=(dp[n][i]-dp[n][i+1])*i;
printf("%.6lf",ans);
}
return 0;
}
Luogu P3802 小魔女帕琪
也是要运用到组合数学。
某神说过:“事实上,概率期望题只要组合组的好,就可以当做细节题做,气氛就变得relax起来。“
神仙写的太好了蒯的:
首先令 \(S\) = \(\sum a_i\)。
然后我们考虑总的状态数即是 \(S!\)。此处不需要考虑如何消除重复元素贡献的原因是两种相同的排列是等概率的两个事件,所以需要多算一次。
然后考虑我们拿出 \(7\) 个元素之后,还有 \(S-7+1=S-6\) 个空等着我们去填,也就是连续一个大可以放到 \(S-6\) 个空里面,也就是
\(\binom{S-6}{1}\) 表示从 \(S-6\) 个可以开大的位置选出一个来开大,\(7!\) 是开大的 \(7\) 个元素的位置方案数,\((S-7)!\) 是剩下元素的排列数,\(\prod a_i\) 是每个元素选一个的方案数。
所以答案就是
化简得:
\(Ans=\frac{7!\cdot \prod\limits_{i=1}^7 a_i}{S(S-1)(S-2)(S-3)(S-4)(S-5)}\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
int n,a[8],minn=1;
long double S,ans,prod=1;
signed main(){
for(int i=1;i<=7;++i) S+=a[i]=read<int>(),prod*=a[i],minn=min(minn,a[i]);
if(!minn) return puts("0.000"),0;
ans=(long double)prod*5040.0/(S*(S-1)*(S-2)*(S-3)*(S-4)*(S-5));
printf("%.3Lf",ans);
return 0;
}
Luogu P2221 [HAOI2012]高速公路
显然可以发现,总贡献为 \(\sum\limits_{i=l}^{r} a_i\cdot(r-i+1)\cdot(i-l+1)\),即 \(a_i\) 的边权乘上覆盖它的区间数即为 \(i\) 这条边的贡献。
化简一下式子,有:
\((r-l+1-rl)\sum a_i+(l+r)\sum i\cdot a_i-\sum i^2\cdot a_i\)
考虑线段树维护 \(a_i\),\(i\cdot a_i\),\(i^2\cdot a_i\) 这三个值的和。
对于修改发现 \(i\cdot a_i\) 的改动是要将 \(\Delta\) 乘上 \(\sum i\) 的(等差数列求和),\(i^2\cdot a_i\) 的可以预处理一下 \(\sum i^2\) 再乘上。
这些都是分子,分母即为选出这两个位置的概率的分母,注意一下边化点的边界(上面的 \(r=r-1\))和分子乘 \(2\)(因为反一下左右端点的顺序也可以算一遍)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=1e5+5;
int n,a[N];
#define ls x<<1
#define rs x<<1|1
struct segmenttree{int sum1,sum2,sum3;}seg[N<<2];
int t[N<<2],lazy[N<<2];
void pushdown(int x,int l,int r){
if(!lazy[x]) return;
int mid=l+r>>1;
seg[ls].sum1+=lazy[x]*(mid-l+1);
seg[ls].sum2+=lazy[x]*(mid+l)*(mid-l+1)/2;
seg[ls].sum3+=lazy[x]*t[ls];
seg[rs].sum1+=lazy[x]*(r-mid);
seg[rs].sum2+=lazy[x]*(r+mid+1)*(r-mid)/2;
seg[rs].sum3+=lazy[x]*t[rs];
lazy[ls]+=lazy[x],lazy[rs]+=lazy[x],lazy[x]=0;
}
void build(int x,int l,int r){
if(l==r) {t[x]=l*l;return;}
int mid=l+r>>1;
build(ls,l,mid),build(rs,mid+1,r);
t[x]=t[ls]+t[rs];
}
void update(int x,int l,int r,int xl,int xr,int v){
if(xl<=l&&xr>=r){
seg[x].sum1+=v*(r-l+1);
seg[x].sum2+=v*(r+l)*(r-l+1)/2;
seg[x].sum3+=v*t[x];
lazy[x]+=v;
return;
}
pushdown(x,l,r);
int mid=l+r>>1;
if(xl<=mid) update(ls,l,mid,xl,xr,v);
if(xr>mid) update(rs,mid+1,r,xl,xr,v);
seg[x].sum1=seg[ls].sum1+seg[rs].sum1;
seg[x].sum2=seg[ls].sum2+seg[rs].sum2;
seg[x].sum3=seg[ls].sum3+seg[rs].sum3;
}
segmenttree query(int x,int l,int r,int xl,int xr){
if(xl<=l&&xr>=r) return seg[x];
pushdown(x,l,r);
int mid=l+r>>1;
if(xl>mid) return query(rs,mid+1,r,xl,xr);
if(xr<=mid) return query(ls,l,mid,xl,xr);
segmenttree tmp1=query(ls,l,mid,xl,xr),tmp2=query(rs,mid+1,r,xl,xr);
tmp1.sum1+=tmp2.sum1;
tmp1.sum2+=tmp2.sum2;
tmp1.sum3+=tmp2.sum3;
return tmp1;
}
#undef ls
#undef rs
signed main(){
n=read<int>();
build(1,1,n);
for(int Q=read<int>();Q;--Q){
char opt[3];
scanf("%s",opt);
if(opt[0]=='C'){
int x=read<int>(),y=read<int>()-1,z=read<int>();
update(1,1,n,x,y,z);
}
else{
int x=read<int>(),y=read<int>()-1;
segmenttree tmp=query(1,1,n,x,y);
int fz=2*((y-x+1-x*y)*tmp.sum1+(y+x)*tmp.sum2-tmp.sum3),fm=(y-x+2)*(y-x+1);
int d=__gcd(fz,fm);
printf("%lld/%lld\n",fz/d,fm/d);
}
}
return 0;
}
CF850F Rainbow Balls
黑题,确实神。
考虑期望 \(dp\),对于每一轮操作,相当于选定一个颜色,让所有的球都变为这个颜色,所以我们假设当前要让所有球变为红色,球就分成了红球和非红球两类。
设 \(f_i\) 为现在有 \(i\) 个红球,把所有球变为红色的期望操作次数,总球数为 \(s\)。
边界:\(f_s=0\),\(f_0\) 不存在。
对于 \(f_i\) 来说,有 \(i\) 个红球,\((s-i)\) 个非红球,
随机选两个球,有三种情况:
- 红+非红 \(\Rightarrow\) 红色球数 +1
- 非红+红 \(\Rightarrow\) 红色球数 -1
- 红+红/非红+非红 \(\Rightarrow\) 当前红色球数
从 \(f_i\) 转移到 \(f_{i-1},f_{i+1}\) 的概率均为 \(p_i=\frac{i(s-i)}{s(s-1)}\),第三种情况(转移回 \(f_i\))的概率为 \((1-2p_i)\)
所以:\(f_i=p_if_{i-1}+p_if_{i+1}+(1-2p_i)f_i+v\)
考虑 \(v\) 是个什么东西。
由于不能到 \(0\) 这个状态,所以 \(i\) 只能算到达那些走到 \(s\) 的贡献。
那么 \(v\) 相当于走到 \(s\) 的概率,也就是走一步的期望,
也就是数轴上一个点 \(x\) 每次等概率向左或者向右走,求走到 \(0\) 之前到达 \(s\) 的概率。
这个是个经典问题,
设 \(g_i\) 表示 \(i\) 到 \(s\) 的概率,那么 \(g_0=0,g_s=1\)
\(g_i=pg_{i-1}+pg_{i+1}+(1-2p)g_i\)
那么 \(g_i-g_{i-1}=g_{i+1}-g_i\)
等差数列,可得概率为 \(\frac{i}{s}\),所以 \(v=\frac{i}{s}\)。
最终的方程为:\(f_i=p_if_{i-1}+p_if_{i+1}+(1-2p_i)f_i+\frac{i}{s}\)
化简得:\(2f_i=f_{i-1}+f_{i+1}+\frac{s-1}{s-i}\)
这个方程是有解的,因为 \(2f_1=f_2+1\),\(2f_{s-1}=f_{s-2}+s-1\)
联立这些个方程后,发现 \(f_1=\frac{(s-1)^2}{s}\),剩下的就递推解决了。
枚举所有最终颜色的情况,最终 \(Ans=\sum\limits_{i=1}^n f_{a_i}\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=25e7+5,mod=1e9+7;
int n,a[2505],s,ans,maxm;
int p,pp;
int qpow(int x,int y,int res=1){
while(y){
if(y&1) res=res*x%mod;
x=x*x%mod,y>>=1;
}
return res;
}
signed main(){
n=read<int>();
for(int i=1;i<=n;++i) s+=a[i]=read<int>(),maxm=max(maxm,a[i]);
sort(a+1,a+1+n);
p=(s-1)*(s-1)%mod*qpow(s,mod-2)%mod,pp=(2*p%mod-1+mod)%mod;
int j=1;
while(a[j]==1) ans=(ans+p)%mod,++j;
while(a[j]==2) ans=(ans+pp)%mod,++j;
for(int i=3;i<=maxm;++i){
int now=((2*pp%mod-p+mod)%mod-(s-1)%mod*qpow(s-i+1,mod-2)%mod+mod)%mod;
while(a[j]==i) ans=(ans+now)%mod,++j;
p=pp,pp=now;
}
printf("%lld",ans);
return 0;
}
未完待续……