2020 Multi-University Training Contest 6 - 1010 Expectation 位运算按位拆分+矩阵树定理
问题描述
题解
题目是求随机选出一个生成树的权值期望,突破点在于生成树的权值定义,是树上所有边权按位与之后的结果。期望最基础的性质就是期望的和等于和的期望,即\(E(X + Y) = E(X) + E(Y)\)。知道这个性质那么有一个很正常的想法就是拆位,因为每一位在按位与的过程中都是相互独立的。
现在我们直接枚举二进制的每一位\(i\),对于这一位,如果\(w_{u, v}\)的第\(i\)位不为\(0\),那么我们就直接在\(u, v\)直接连一条边。那么对于这张新图,每有一个生成树,那么都会对答案贡献\(\frac{2^i}{sum}\),\(sum\)是指原图中生成树的个数。用无向图的矩阵树定理就能求出生成树的个数,这道题的整体复杂度是\(O(Tn^3\log{w} )\)。
\(\mathrm{Code}\)
#include<bits/stdc++.h>
using namespace std;
const int maxm = 10000 + 7;
const int maxn = 100 + 3;
const int mod = 998244353;
int T, n, m;
int u[maxm], v[maxm], w[maxm];
int a[maxn][maxn];
#define int long long
int ksm(int b,int p){int ret=1;while(p){if(p&1)ret=1ll*ret*b%mod;b=1ll*b*b%mod;p>>=1;}return ret;}
#undef int
void Init(void) {
scanf("%d%d", &n, &m);
}
void add(int x, int y) {
// if(x > y) return;
a[x][x]++, a[y][y]++;
a[x][y]--, a[y][x]--;
}
int Gauss() {
int ans=1;
for(int i=1;i<n;++i) {
for(int k=i+1;k<n;++k) {
while(a[k][i]) {
int d=a[i][i]/a[k][i];
for(int j=i;j<n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
std::swap(a[i],a[k]),ans=-ans;
}
}
ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
}
return ans;
}
long long ans;
void Work(void) {
for(int i = 1; i <= m; i++) {
scanf("%d%d%d", &u[i], &v[i], &w[i]);
}
for(int p = 0; p < 32; p++) {
memset(a, 0, sizeof(a));
for(int i = 1; i <= m; i++) {
if(w[i] & (1ll << p)) add(u[i], v[i]);
}
int res = Gauss();
ans =(ans + (long long)(1ll << p) * (long long)res) % mod;
}
memset(a, 0, sizeof(a));
for(int i = 1; i <= m; i++) {
add(u[i], v[i]);
}
int sum = Gauss();
ans = ((ans * ksm((long long)sum, mod - 2)) % mod + mod) % mod;
printf("%lld\n", ans);
}
int main(void) {
scanf("%d", &T);
while(T--) {
ans = 0;
Init();
Work();
}
return 0;
}