2024FJCPC-H.螢火的意志-min-max容斥、Prufer序列(Cayley定理)、高維字首和

yoshinow2001發表於2024-05-29

題目:https://pintia.cn/market/item/1795304158332379136
題意:有一個 \(n(1\leq n\leq 10^9)\) 個點的完全圖,其中有 \(m(m\leq 20)\) 條特殊的邊。每次操作會等機率地選擇這張圖的一棵生成樹,然後將樹上的邊染色。問期望多少次,能把所有 \(m\) 條特殊邊都染上色。


\(f_e\) 表示邊 \(e\) 第一次被染色的時間,特殊邊的集合為 \(S\),經典min-max容斥:
答案是

\[E(\max_{e\in S} f_e)=E(\sum_{T\subset S}(-1)^{|T|-1}\min_{e\in T} f_e)=\sum_{T\subset S} (-1)^{|T|-1} E(\min_{e\in T} f_e) \]

考慮 \(\min_{e\in T} f_e\) 的含義,即對這個邊集來說,有某條邊被染色(選中)的時間,這是經典的幾何分佈——假設選中 \(T\) 中某條邊的機率是 \(p_T\),則期望次數是 $$\sum_{i=1}^\infty i\times (1-p_T)^{i-1}\cdot p_T=\frac{1}{p_T}$$
因此只要計算機率 \(p_T\) 即可

要算選中至少一條邊的機率,再容斥一下:設 \(X_e\) 表示事件“邊 \(e\) 被選中”,則要求的是:

\[p_T=P(\ \bigcup_{e\in T} X_e\ )=\sum_{U\subset T,U\neq \emptyset} (-1)^{|U|-1} P(\ \bigcap_{e\in U}X_e\ ) \]

轉換成算某個邊集所有邊都被選中的機率,注意這裡可千萬別覺得好像“繞回去了”,一開始的問題是所有邊都被選中的次數期望,這裡被我們用兩次容斥轉換成了一個“簡單”得多的機率問題。

這是經典的圖連通計數,強制讓 \(U\) 中的邊連線,問有多少種加邊方案能得到一棵樹,假設有 \(k\) 個連通塊,其大小分別是 \(s_1,\dots,s_k\),則方案是 \(n^{k-2} \prod s_i\),因此機率就是 \(n^{n-k}\prod s_i\).

這樣我們可以在 \(O(2^m\cdot m\cdot \alpha(n))\) 的時間得到每個 \(P(\cap_{e\in U} X_e)\) 的答案,然後要得到 \(p_T\),是個經典高維字首和(參考:https://www.cnblogs.com/heyuhhh/p/11585358.html

實現

注意點: 第二個容斥一定要記得 \(U\neq \emptyset\),我debug的大部分時間都花在這了…

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long ll;
const int MOD=1e9+7;
const int M=20;
int ksm(int a,int b){
    int ret=1;a%=MOD;
    for(;b;b>>=1,a=(ll)a*a%MOD)if(b&1)ret=(ll)ret*a%MOD;
    return ret;
}
int n,m,u[M+1],v[M+1],f[(1<<M)+5],g[(1<<M)+5];
int fa[M*2+5],sz[M*2+5];
vector<int> b;
int find(int x){
    if(x==fa[x])return x;
    return fa[x]=find(fa[x]);
}
bool merge(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy)return false;
    fa[fy]=fx;
    sz[fx]+=sz[fy];
    return true;
}
void dsu_init(){
    rep(i,0,b.size())fa[i]=i,sz[i]=1;
}
void add(int &x,int y){
    x+=y;
    if(x>=MOD)x-=MOD;
}
int main(){
    fastio;
    cin>>n>>m;
    rep(i,0,m-1){
        cin>>u[i]>>v[i];
        b.push_back(u[i]);
        b.push_back(v[i]);
    }
    sort(b.begin(),b.end());
    b.erase(unique(b.begin(),b.end()),b.end());
    rep(i,0,m-1){
        u[i]=lower_bound(b.begin(),b.end(),u[i])-b.begin();
        v[i]=lower_bound(b.begin(),b.end(),v[i])-b.begin();
    }

    for(int S=0;S<(1<<m);S++){
        dsu_init();
        bool ok=true;
        rep(i,0,m-1)if((S>>i)&1)ok&=merge(u[i],v[i]);
        if(!ok)f[S]=0;
        else{
            f[S]=1;
            int block=n-b.size();
            rep(i,0,b.size()-1)if(find(i)==i){
                block++;
                f[S]=(ll)f[S]*sz[i]%MOD;
            }
            f[S]=(ll)f[S]*ksm(ksm(n,n-block),MOD-2)%MOD;
            if((__builtin_popcount(S)&1)==0)f[S]=MOD-f[S];
        }
        g[S]=f[S];
    }


    rep(j,0,m-1)rep(S,0,(1<<m)-1)if(S>>j&1)add(f[S],f[S^(1<<j)]);
    rep(S,0,(1<<m)-1)add(f[S],MOD-g[0]);
    rep(S,0,(1<<m)-1)f[S]=ksm(f[S],MOD-2);

    int ans=0;
    rep(S,0,(1<<m)-1){
        if(__builtin_popcount(S)&1)add(ans,f[S]);
        else add(ans,MOD-f[S]);
    }
    cout<<ans;

    return 0;
}

相關文章