1. 程式人生 > >【學術篇】CF932E Team Work && bzoj5093 圖的價值

【學術篇】CF932E Team Work && bzoj5093 圖的價值

兩個題傳送門

對於CF這道題, 分別考慮每種可能的集合大小, 每個大小為\(k\)的集合數量有\(\binom nk\)個, 所以最後的答案就是
\[\sum_{i=0}^n\binom{n}{i}i^k\]
對於bzoj這道題, 我們分別考慮每個點的貢獻, 這個點可以和其他\(n-1\)個點連任意條邊, 貢獻是\(\sum_{i=0}^{n-1}\binom{n-1}ii^k\)
此時其他\(n-1\)個點間的\(\frac{(n-1)(n-2)}2\)條邊可連可不連, 所以有\(2^{\frac{(n-1)(n-2)}2}\)種選擇, 最後再乘以點數, 我們要求的就是
\[n*2^{\frac{(n-1)(n-2)}2}*\sum_{i=0}^{n-1}\binom{n-1}ii^k\]


我們看這個\(n-1\)非常不爽, 不妨將讀進來的\(n\)直接自減一下代替\(n-1\), 那我們要求的就是
\[(n+1)*2^{\frac{n(n-1)}2}*\sum_{i=0}^n\binom nii^k\]
前面的乘法和快速冪都賊jr簡單, 所以我們問題的關鍵也就變得和上面一樣, 求
\[\sum_{i=0}^n\binom nii^k\]

然後一看這個式子直接算肯定是完全過不去的, 我們就要化式子.
我們首先就要根據第二類斯特林數的性質把\(k\)次冪轉化為下降冪.
(你問什麼是下降冪? 就是說\(n^\underline{k}=n(n-1)(n-2)\cdots(n-k+1)\)

這樣的啊~如果你願意也可以看成\(n!/(n-k)!\))
\[=\sum_{i=0}^n\binom{n}{i}\sum_{j=0}^k\begin{Bmatrix}k\\j\end{Bmatrix}i^{\underline{j}}\]
然後把組合數和下降冪都展開發現可以前後約掉一個\(i!\)
\[=\sum_{i=0}^n\frac{n!}{(n-i)!}\sum_{j=0}^k\begin{Bmatrix}k\\j\end{Bmatrix}\frac 1{(i-j)!}\]
然後收拾下式子, 把\(n!\)提出來, \(\sum\)提前,
\[=n!\sum_{i=0}^n\sum_{j=0}^k\begin{Bmatrix}k\\j\end{Bmatrix}\frac1{(n-i)!}\cdot\frac{1}{(i-j)!}\]

然後我們發現後面可以搞成一個組合數的形式, 我們考慮乘一個\((n-j)!\)化成組合數, 然後再把它除掉...
\[=n!\sum_{i=0}^n\sum_{j=0}^k\begin{Bmatrix}k\\j\end{Bmatrix}\binom {n-j}{n-i}\cdot\frac 1{(n-j)!}\]
我們把只和\(j\)有關的式子提到前面, 順便把\(n!\)重新乘進去
\[=\sum_{j=0}^k\begin{Bmatrix}k\\j\end{Bmatrix}\frac{n!}{(n-j)!}\sum_{i=0}^n\binom{n-j}{n-i}\]
由於\(i\)\(0\)取到\(n\), 所以組合數一定能取遍\([0,n-j]\)的每個數, 而且比\(n-j\)大的部分組合數都是0. 所以由組合數的性質可知\[\sum_{i=0}^n\binom{n-j}{n-i}=2^{n-j}\]
所以最終可以把原式化為
\[=\sum_{j=0}^k\begin{Bmatrix}k\\j\end{Bmatrix}n^{\underline{j}}2^{n-j}\]
眾所周知, 第二類斯特林數說的是

\(n\)個球放到\(k\)個盒子中, 要求盒子非空的方案數. (盒子和球均無標號)

所以如果前\(n-1\)個球放到了\(k-1\)個盒子中, 那麼第\(n\)個球只好放到第\(k\)個集合中; 而如果前\(n-1\)個球放進了\(k\)個盒子, 那麼第\(n\)個球只要放到任意一個盒子中就行了.
\[\begin{Bmatrix}n\\k\end{Bmatrix}=\begin{Bmatrix}n-1\\k-1\end{Bmatrix}+\begin{Bmatrix}n-1\\k\end{Bmatrix}*k\]
很顯然這樣處理斯特林數是\(O(k^2)\)的, 對於CF上這道題來說, \(k\leq5000\), 這樣做就可以跑過了. AC程式碼:

#include <cstdio>
const int p=1e9+7;
int s[5050][5050],cur[5050];
int qpow(int a,long long b,int s=1){
    for(;b;b>>=1,a=1ll*a*a%p)
        if(b&1) s=1ll*s*a%p;
    return s;
}
int main(){
    int n,k,ans=0; scanf("%d%d",&n,&k);
    s[0][0]=cur[0]=1;
    for(int i=1;i<=k;++i){
        cur[i]=1ll*cur[i-1]*(n-i+1)%p;
        for(int j=1;j<=k;++j){
            s[i][j]=s[i-1][j-1]+1ll*s[i-1][j]*j%p;
            if(s[i][j]>=p) s[i][j]-=p;
        }
    }
    for(int i=0;i<=k&&i<=n;++i){
        ans+=1ll*qpow(2,n-i,s[k][i])*cur[i]%p;
        if(ans>=p) ans-=p;
    } printf("%d",ans);
}

但是對於bzoj上這道題來說, \(k\leq 200000\), 再\(O(k^2)\)預處理就不行了. 我們考慮還有沒有別的方法來處理斯特林數.
我們注意到實際上我們用到的斯特林數只有第\(k\)行這一行的數, 我們沒有必要求出前\(k-1\)行來.
我們假設現在有\(n\)個球要放到\(m\)個盒子中, 我們可以讓先給盒子標號, 最後的方案數除以\(m!\)即可.
然後我們列舉空盒子的數量\(k\), 一共有\(\binom mk\)種空盒子的選擇方案, 然後\(n\)個球可以在\((m-k)\)個盒子中隨便放.
然後根據容斥我們就可以得出
\[\begin{Bmatrix}n\\m\end{Bmatrix}=\frac 1{m!}\sum_{k=0}^m(-1)^k\binom mk(m-k)^n\]
我們把組合數拆開
\[\begin{Bmatrix}n\\m\end{Bmatrix}=\sum_{k=0}^m\frac {(-1)^k}{k!}\cdot\frac{(m-k)^n}{(m-k)!}\]
然後我們令\(f(x)=\frac{(-1)^x}{x!}, g(x)=\frac{x^n}{x!}\), 就可以發現
\[\begin{Bmatrix}n\\m\end{Bmatrix}=\sum_{k=0}^mf(k)*g(m-k)\]
是個明顯的卷積形式, 而題目中又給了\(998244353\)作為模數, 自然NTT一下就可以咯~(其實這是我寫的第一道卷積非模板題)程式碼:

#include <cstdio>
#include <algorithm>
const int N=606060,p=998244353;
int wn[20],nw[20],f[N],g[N],rev[N],n,lg;
int m,k,fac[N],cur[N],inv[N];
int qpow(int a,long long b,int s=1){
    for(;b;b>>=1,a=1ll*a*a%p)
        if(b&1ll) s=1ll*s*a%p;
    return s;
}
void calcw(){
    int x=qpow(3,p-2);
    for(int i=0;i<20;++i){
        wn[i]=qpow(3,(p-1)/(1<<i));
        nw[i]=qpow(x,(p-1)/(1<<i));
    }
}
void init(){        
    for(int i=0;i<n;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<lg);
    fac[0]=inv[0]=cur[0]=1;
    for(int i=1;i<=k;++i) fac[i]=1ll*fac[i-1]*i%p;
    for(int i=1;i<=k;++i) cur[i]=1ll*cur[i-1]*(m-i+1)%p;
    inv[k]=qpow(fac[k],p-2);
    for(int i=k-1;i;--i) inv[i]=1ll*inv[i+1]*(i+1)%p;
    for(int i=0;i<=k;++i){
        f[i]=(i&1?-inv[i]+p:inv[i])%p;
        g[i]=1ll*qpow(i,k)*inv[i]%p;
    }
}
void ntt(int *y,bool f){
    for(int i=0;i<n;++i) if(i<rev[i]) std::swap(y[i],y[rev[i]]);
    for(int m=2,id=1;m<=n;m<<=1,++id){
        for(int k=0;k<n;k+=m){
            int w=1,wm=f?wn[id]:nw[id];
            for(int j=0;j<m>>1;++j){
                int &a=y[k+j]; int &b=y[k+j+m/2];
                int u=a%p,t=1ll*w*b%p;
                a=u+t; if(a>=p) a-=p;
                b=u-t; if(b<0) b+=p;
                w=1ll*w*wm%p;
            }
        }
    } int x=qpow(n,p-2);
    if(!f) for(int i=0;i<n;++i) y[i]=1ll*y[i]*x%p;
}
int main(){
    scanf("%d%d",&m,&k); --m;
    for(n=1;n<=k+1;n<<=1,++lg); n<<=1;
    init(); calcw(); ntt(f,1); ntt(g,1);    
    for(int i=0;i<n;++i) f[i]=1ll*f[i]*g[i]%p;
    ntt(f,0); int ans=0;
    for(int i=0;i<=k&&i<=m;++i){
        ans+=1ll*qpow(2,m-i,cur[i])*f[i]%p;
        if(ans>=p) ans-=p;
    } 
    ans=1ll*ans*qpow(2,1ll*m*(m-1)/2,m+1)%p;
    printf("%d",ans);
}