1. 程式人生 > 實用技巧 >2020杭電多校 6J / HDU 6836 - Expectation (數學、計數)

2020杭電多校 6J / HDU 6836 - Expectation (數學、計數)

HDU 6836 - Expectation


題意

定義一顆生成樹的值為所有邊權按位與的答案

給定一張圖,求生成樹的值的期望




思路

既然是取按位與後的值作為權值,那麼可以從二進位制的方向考慮答案

假如對於某一棵生成樹,它的答案在二進位制的第\(i\)位上不為\(0\)

那也就等同於這棵生成樹的所有邊的邊權第\(i\)位都不為\(0\)

所以可以考慮二進位制上每一位對答案的貢獻

先使用題目給定的所有邊進行一次生成樹計數,表示總共可以生成多少棵不同的生成樹,記作\(tot\)

然後考慮二進位制上的右數第\(i\)位(表示\(2^{i-1}\)

既然要使得答案的第\(i\)位為\(1\),上面說過,所有邊邊權第\(i\)

位都必須為\(1\)

所以我們遍歷一遍所有邊,將第\(i\)位為\(1\)的邊作為可行邊

用第\(i\)位所有的可行邊再做生成樹計數,將此時生成數的數量記作\(cnt_i\)

那麼第\(i\)位在答案中為\(1\)的概率就是\(\frac{cnt_i}{tot}\)

又因為第\(i\)位對答案的貢獻為\(2^{i-1}\)

所以數學期望為\(2^{i-1}\frac{cnt_i}{tot}\)

由於題目範圍有\(10^9\),所以大概要考慮到\(2^{30}\)

所以最終答案即為\(\sum_{i=0}^{30}2^{i-1}\frac{cnt_i}{tot}\)

(對於生成樹計數,直接套板子即可,程式碼裡的板子是使用這篇部落格的:

《圖論 —— 生成樹 —— 生成樹計數》




程式碼

(265ms/5000ms)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int N=105,M=10050;

int x[M],y[M],v[M];
ll K1[N][N],K2[N][N];

ll qpow(ll a,ll n)
{
    ll r=1;
    while(n)
    {
        if(n&1)
            r=(r*a)%mod;
        n>>=1;
        a=(a*a)%mod;
    }
    return r;
}

ll gauss(int n,ll K[N][N])
{
    ll res=1;
    for(int i=1;i<=n-1;i++)
    {
        for(int j=i+1;j<=n-1;j++)
        {
            while(K[j][i])
            {
                ll t=K[i][i]/K[j][i];
                for(int k=i;k<=n-1;k++)
                    K[i][k]=(K[i][k]-t*K[j][k]+mod)%mod;
                swap(K[i],K[j]);
                res=-res;
            }
        }
        res=(res*K[i][i])%mod;
    }
    return (res+mod)%mod;
}

void solve()
{
    int n,m;
    cin>>n>>m;
    memset(K1,0,sizeof K1);
    for(int i=1;i<=m;i++)
    {
        cin>>x[i]>>y[i]>>v[i];
        K1[x[i]][x[i]]++;
        K1[y[i]][y[i]]++;
        K1[x[i]][y[i]]--;
        K1[y[i]][x[i]]--;
    }
    ll d=gauss(n,K1),invd=qpow(d,mod-2),ans=0; //計算逆元
    for(int i=0;i<=30;i++)
    {
        memset(K2,0,sizeof K2);
        ll tmp=1<<i;
        for(int j=1;j<=m;j++)
        {
            if(v[j]&tmp)
            {
                K2[x[j]][x[j]]++;
                K2[y[j]][y[j]]++;
                K2[x[j]][y[j]]--;
                K2[y[j]][x[j]]--;
            }
        }
        ans=(ans+tmp*gauss(n,K2)%mod*invd%mod)%mod; //權值*概率=期望
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int T;cin>>T;
    while(T--)
        solve();
    return 0;
}