2020杭电多校 6J / 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;
}