2020杭電多校 6J / HDU 6836 - Expectation (數學、計數)
阿新 • • 發佈:2020-08-06
題意
定義一顆生成樹的值為所有邊權按位與的答案
給定一張圖,求生成樹的值的期望
思路
既然是取按位與後的值作為權值,那麼可以從二進位制的方向考慮答案
假如對於某一棵生成樹,它的答案在二進位制的第\(i\)位上不為\(0\)
那也就等同於這棵生成樹的所有邊的邊權第\(i\)位都不為\(0\)
所以可以考慮二進位制上每一位對答案的貢獻
先使用題目給定的所有邊進行一次生成樹計數,表示總共可以生成多少棵不同的生成樹,記作\(tot\)
然後考慮二進位制上的右數第\(i\)位(表示\(2^{i-1}\))
既然要使得答案的第\(i\)位為\(1\),上面說過,所有邊邊權第\(i\)
所以我們遍歷一遍所有邊,將第\(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; }