[CF850F]Rainbow Balls
\(\text{Tip}\):感谢此篇题解对我的巨大帮助,故记录一下式子的推导过程。
\(\text{Problem}\):题目链接
\(\text{Solution}\):
有多种颜色的球,且要求最后剩下的球颜色相同。故考虑枚举最后留下的球的颜色 \(x\)。
记 \(F_{i}\) 表示当前有 \(i\) 个颜色为 \(x\) 的球,把所有球都变成颜色为 \(x\) 的期望时间,再记 \(S\) 表示球的总和,即 \(S=\sum\limits_{i=1}^{n}a_{i}\)。显然 \(F_{0}\) 这个状态不存在,且 \(F_{S}=0\)。记 \(P_{i}\) 表示当前有 \(i\) 个颜色为 \(x\) 的球时,选出两个球颜色不同且其中至少有一个球颜色为 \(x\) 的概率,则 \(P_{i}=\cfrac{i(s-i)}{s(s-1)}\)。记 \(v_{i}\) 表示每次转移时贡献的期望时间,则对于 \(\forall i\in[1,S-1]\)易得:
此处有个很关键的地方,就是 \(v_{i}\not=1\)。考虑其原因,发现当 \(i\leq0\) 时答案不存在,所以需要考虑能从 \(i\) 到达 \(S\) 的贡献,而走一步的期望就相当于 \(i\) 能走到 \(S\) 的概率。而这是个经典问题,简述为:数轴上有一个点 \(p\) 每次等概率向左或向右走,也可以不走,问能走到 \(S\) 的概率。该问题解法如下:
记 \(G_{i}\) 表示从 \(i\) 能走到 \(S\) 的概率,那么有:
其中边界条件显然为 \(G_{0}=0,G_{S}=1\)。上式可解得 \(2G_{i}=G_{i-1}+G_{i+1}\)。发现 \(G_{i+1}-G_{i}=G_{i}-G_{i-1}\),则对于数列 \(\{G_{i}-G_{i-1}\}(1\leq i\leq S)\),构成一个等差数列 。
故得到 \(G_{i}=\cfrac{i}{S}\)。
于是 \(v_{i}=G_{i}=\cfrac{i}{S}\),则转移方程为:
着手拆开这个式子,得到:
将 \(P_{i}=\frac{i(S-i)}{S(S-1)}\) 代入,得到:
发现通过这个式子,可以对于所有的 \(i\in[2,S]\),把 \(F_{i-1}-F_{i}\) 转换为 \(F_{1}-F_{2}\),考虑求出它。
发现 \(F_{0}\) 不存在。于是对于 \(i=1\),可以算出:
发现 \(P_{1}=\frac{1}{S}\),则得到:\(F_{2}=2F_{1}-1\)
故考虑如何使用上面的关键性质。发现 \(F_{S}=0\),则 \(F_{1}=F_{1}-F_{S}\),故有:
考虑如何快速的把 \(F_{i-1}-F_{i}\) 快速转化为与 \(F_{1}-F_{2}\) 有关的式子:
发现对于 \(\sum\limits_{i=2}^{S}F_{i-1}-F_{i}\) 来说,不必 \(O(S^2)\) 爆求式子的原因是后面枚举贡献的式子重复。发现对于 \(j\) 来说,计算 \(\cfrac{S-1}{S-j}\) 的次数为 \((S-j)\) 次。则得到:
于是把 \(F_{2}=2F_{1}-1\) 代入上式,即可用 \(S\) 表达出 \(F_{1}\):
然后我们可以通过 \(F_{1}\) 求出 \(F_{2}\),那么可以直接通过 \(O(S\log P)\) 的时间复杂度求出 \(F\) 的每一项(\(P\) 为本题中的模数)。
发现 \(S=\sum\limits_{i=1}^{n}a_{i}\approx 2.5\times 10^{8}\),数组开不下。考虑最终答案是 \(\sum\limits_{i=1}^{n}F_{a_{i}}\),故记 \(W=max\{a_{i}\}\),我们只需要求到 \(F_{W}\) 即可,时间复杂度为 \(O(W\log P)\),空间复杂度为 \(O(W)\),发现 \(W\leq10^{5}\),可以通过本题。
\(\text{Code}\):
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <stack>
#include <map>
#include <bitset>
#define ri register
#define inf 0x7fffffff
#define E (1)
#define mk make_pair
#define int long long
//#define double long double
using namespace std; const int N=200010, Mod=1e9+7;
inline int read()
{
int s=0, w=1; ri char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch-'0'), ch=getchar();
return s*w;
}
void print(int x) { if(x<0) x=-x, putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); }
int n,a[N],dp[N],S;
inline int ksc(int x,int p) { int res=1; for(;p;p>>=1, x=x*x%Mod) if(p&1ll) res=res*x%Mod; return res; }
signed main()
{
n=read();
for(ri int i=1;i<=n;i++) a[i]=read(), S+=a[i];
dp[1]=ksc(S,Mod-2)%Mod*(S-1)%Mod*(S-1)%Mod;
dp[2]=dp[1]*2-1, dp[2]%=Mod;
for(ri int i=3;i<=min((int)1e5,S-1);i++) dp[i]=(dp[i-1]*2-dp[i-2]-(S-1)%Mod*ksc(S-i+1,Mod-2)%Mod)%Mod, dp[i]=(dp[i]+Mod)%Mod;
int res=0;
for(ri int i=1;i<=n;i++) res=(res+dp[a[i]])%Mod;
printf("%lld\n",res);
return 0;
}