CodeForces 891E Lust(生成函式)
大致題意:給出一個數列{an},每次隨機的選擇一個數字ai,產生出了ai之外其餘所有數字之積的貢獻,然後ai減一。現在進行k次這樣的操作,問最後者k次操作產生的貢獻之和是多少。
這個貢獻看起來很複雜,但是實際上,我們可以把每一次操作的貢獻,看作是操作前後所有數字的乘積之差。具體推導如下:
這樣我們就證明了一次操作前後的貢獻就是操作前後兩次所有數字乘積的差。那麼,顯然多次操作中間的乘積可以抵消,最後的貢獻就是初始時所有數字的乘積與最終所有數字的乘積。初始所有數字的乘積已知,我們要求貢獻之和的期望,相當於只需要求最終乘積的期望。
我們考慮每個數字被選中{bi}次,那麼這個數字最後的貢獻就是ai-bi,顯然Σbi=k。那麼我們可以寫出最後乘積的期望的表示式:
接下來考慮這個式子怎麼求。這裡的{bi}是每個位置數字被取的次數,這個次數的取值在區間[0,k]上,對於每一個取值,我們都對應一個(ai-bi)/bi的數值。我們令f表示,用生成函式f(x)的係數表示相應取的次數情況下的貢獻。例如:表示第i個數字被取j次所產生的貢獻為。那麼f可以寫成:
對於這個ai-x由於n比較小,所以我們可以暴力O(N^2)直接去展開成一個多項式。可以得到Π(ai-x)=Σci(x^i)。那麼有:
現在我們回顧一下,我們要求的東西到底是什麼。我們要求的是進行k次操作之和的貢獻之和的期望。那麼這個期望在f的生成函式中怎麼體現呢?注意到我們之前是按照進行操作的次數展開的,x的k次項的係數表示取k次的方案數。所以說我們要求的就是x的k次項的係數。
進行到這一步,我們可以考慮再次把e^(nx)給展開,於是就變成了兩個多項式的卷積。可以知道x的課次項係數是:
如此我們就可以算出x的k次項的係數。有了係數之後,我們考慮把[x^k]f回代入E的表達是,也即把前面的係數乘上,得:
最後用初始的乘積減去這個E即可。時間複雜度只要在O(N^2)展開得到{ci}的時候,其餘都是O(N)。總體來說這題比較巧妙的運用了冪級數型生成函式和指數型生成函式兩種生成函式,使得在推導的過程中能夠相互轉換,變成簡單形式後暴力求解,最後再回代。具體見程式碼:
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int mod = 1e9 + 7;
const int modinv2 = 5e8 + 4;
const int G = 3;
const int N = 5010;
int fac[N],ifac[N],inv[N],pw[N];
int a[N],b[N],c[N],n,k;
void init()
{
fac[0]=ifac[0]=inv[0]=1;
fac[1]=ifac[1]=inv[1]=1;
for(int i=2;i<N;i++)
{
fac[i]=fac[i-1]*(LL)i%mod;
inv[i]=(mod-mod/i)*(LL)inv[mod%i]%mod;
ifac[i]=ifac[i-1]*(LL)inv[i]%mod;
}
}
int main()
{
LL ans=1;
init(); pw[0]=1;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
ans=ans*a[i]%mod;
pw[i]=(LL)pw[i-1]*n%mod;
}
//cout<<ans<<endl;
c[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<i;j++) b[j]=(LL)c[j]*a[i]%mod;
for(int j=0;j<i;j++) b[j+1]=(b[j+1]-c[j]+mod)%mod;
memcpy(c,b,sizeof(c));
}
for(int i=0,t=1,T=1;i<=n;i++)
{
//cout<<c[i]<<' ';
c[i]=(LL)c[i]*t%mod*T%mod;
t=(LL)t*inv[n]%mod; T=(LL)T*(k-i)%mod;
}
//cout<<endl;
for(int i=0;i<=n;i++) ans=(ans-c[i]+mod)%mod;
printf("%lld\n",(ans+mod)%mod);
return 0;
}