\(\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;
}