1. 程式人生 > >[2018HN省隊集訓D8T3] 水果拼盤

[2018HN省隊集訓D8T3] 水果拼盤

char 分享 ans .cn 普通 參考 自帶 value tps

[2018HN省隊集訓D8T3] 水果拼盤

題意

給定 \(n\) 個集合, 每個集合包含 \([1,m]\) 中的一些整數, 在這些集合中隨機選取 \(k\) 個集合, 求這 \(k\) 個集合的並集的權值的期望.

一個集合的權值定義為, 對於所有 \([1,m]\) 的整數, 若集合中含有 \(i\) 則產生 \(a_i\) 的貢獻, 否則產生 \(b_i\) 的貢獻.

\(n\le 1\times 10^5, m\le 18,k\le 25\)

題解

好像只有我一個寫了一些玄學FWT操作...別人都是組合數直接碾的qaq

顯然我們可以通過求所有最終集合的生成概率來計算出最終期望. 而這個概率顯然就是個或卷積的形式.

於是我們可以FWT一發.

接著我們發現直接FWT卷 \(k\) 次可能會有重復的方案. (就像[BZOJ 3771] Triple那題). 於是我們需要考慮容斥.

然而這次是廣義容斥, 普通二項式反演出來是假的.

stdcall&棟棟說過廣義容斥瞎換一波系數就過了, 於是思考一些奇怪的東西來湊容斥系數.

FWT卷 \(k\) 次後得到的方案數是 \(n^k\), 而我們實際上需要的不重復的方案數應該是 \(n^{\underline k}\) (卷積出來的方案有序, 要自帶一個全排列), 那麽我們需要用一些玄學系數用 \(n^k\) 湊出 \(n^{\underline k}\).

註意到其實 \(n^{\underline k}\)

就是一個普通多項式, 那麽我們可以直接算出這個多項式的每一項系數把它作為容斥系數.

實際上就是帶符號第一類斯特林數. 用這個系數容斥一下就好了.

FWT一次後的點值可以重復使用, 所以總時間復雜度是 \(O(\sum|S|+(k+m)2^m)\).

參考代碼

#include <bits/stdc++.h>

const int MAXK=27;
const int MAXL=1e6+10;
const int MOD=998244353;

int n;
int m;
int k;
int a[MAXL];
int pw[MAXK];
int ans[MAXL];
int cof[MAXK];
int c[MAXK][2];

void FWT(int*,int);
void IFWT(int*,int);
inline int ReadInt();
inline int Pow(int,int,int);

int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<m;i++)
        scanf("%d",c[i]+1);
    for(int i=0;i<m;i++)
        scanf("%d",c[i]);
    cof[0]=1;
    for(int i=0;i<k;i++){
        for(int j=i+1;j>0;j--)
            cof[j]=(cof[j-1]-1ll*cof[j]*i%MOD+MOD)%MOD;
        cof[0]=(MOD-1ll*cof[0]*i%MOD)%MOD;
    }
    for(int i=0;i<n;i++){
        int cnt=ReadInt(),s=0;
        while(cnt--)
            s|=(1<<(ReadInt()-1));
        ++a[s];
    }
    int maxs=1<<m;
    FWT(a,maxs);
    pw[0]=1;
    for(int s=0;s<maxs;s++){
        for(int i=1;i<=k;i++)
            pw[i]=1ll*pw[i-1]*a[s]%MOD;
        for(int i=0;i<k;i++)
            ans[s]=(ans[s]+1ll*pw[k-i]*cof[k-i])%MOD;
    }
    IFWT(ans,maxs);
    int cnt=0;
    int sum=0;
    for(int s=0;s<maxs;s++){
        (cnt+=ans[s])%=MOD;
        for(int i=0;i<m;i++)
            sum=(sum+1ll*c[i][(s>>i)&1]*ans[s])%MOD;
    }
    printf("%lld\n",1ll*sum*Pow(cnt,MOD-2,MOD)%MOD);
    return 0;
}

inline void FWT(int* a,int len){
    for(int i=1;i<len;i<<=1)
        for(int j=0;j<len;j+=(i<<1))
            for(int k=0;k<i;k++){
                a[j+k+i]+=a[j+k];
                a[j+k+i]=(a[j+k+i]>=MOD?a[j+k+i]-MOD:a[j+k+i]);
            }
}

inline void IFWT(int* a,int len){
    for(int i=1;i<len;i<<=1)
        for(int j=0;j<len;j+=(i<<1))
            for(int k=0;k<i;k++){
                a[j+k+i]-=a[j+k];
                a[j+k+i]=(a[j+k+i]<0?a[j+k+i]+MOD:a[j+k+i]);
            }
}

inline int ReadInt(){
    int x=0;
    register char ch=getchar();
    while(!isdigit(ch))
        ch=getchar();
    while(isdigit(ch)){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}

inline int Pow(int a,int n,int p){
    int ans=1;
    while(n>0){
        if(n&1)
            ans=1ll*a*ans%p;
        a=1ll*a*a%p;
        n>>=1;
    }
    return ans;
}

技術分享圖片

[2018HN省隊集訓D8T3] 水果拼盤