题意:
首先定义了一棵生成树的重量:这棵树所有边的按位与(AND)
给出一个无向连通图,随机挑选一个生成树,问这个生成树的重量的期望是多少。
题解:
做法:
数学期望的性质:
本题中给出的生成树的重量的定义:所有边的边权的按位与,我们可以拆开,按位来考虑。
利用数学期望的性质,可以得到下面的式子:
下面我们只要考虑每一位就可以了。
第 位要怎样才能是1呢, 要所有边的边权的第 位都是1。
我只要把所有的第 位是 的边找出来,然后计算一下这些边能够构成多少个生成树。那这个生成树的数量就是重量第 位为 的所有生成树。设这个数量为 ,这张图总生成树数量为 。
那期望就能算出来, 。
依次计算32位,最后相加就行了。
然后生成树个数的计算,使用矩阵树定理就能计算出来。
矩阵树定理:
矩阵树定理参考博客地址
百度百科:
求出基尔霍夫矩阵之后,只要求它的一个代数余子式即可。下面的模板是求 的代数余子式,即 ,因为 一定是偶数,所以也就是求去掉第 行,第 列之后的 阶方阵的行列式。
行列式是转换成上三角之后,对角线元素乘积直接得到。
另:高斯消元部分模板是采用辗转相除的方式消去下面的行,因为答案要取模。与求GCD的方式类似,不断相除,总有一个会变成 。
高斯消元过程还涉及到交换两行的操作,根据行列式的性质,交换两行,行列式变为原来的负数。
代码:
/*
* @file 1010.cpp
* @path D:\code\ACM\HDU\no.6\1010.cpp
* @author Xiuchen
* @date 2020-08-07 11:47:57
*/
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<cmath>
#include<math.h>
#include<iostream>
#include<algorithm>
//#define DEBUG
#define dbg(x) cout << #x << " = "<< (x) << endl
#define dbg2(x1,x2) cout << #x1 << " = " << x1 << " " << #x2 << " = " << x2 << endl
#define dbg3(x1,x2,x3) cout<< #x1 << " = " << x1 << " " << #x2 << " = " << x2 << " " << #x3 << " = " << x3 <<endl
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
const int maxn = 11000;
const ll mod = 998244353;
int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
int t;
int n, m;
struct node
{
int u, v;
ll w;
} edge[maxn];
int k[110][110];
ll sum = 0;
ll qpow(ll x, ll y){
x %= mod;
ll ans = 1, base = x;
while(y){
if(y & 1) ans = ans * base % mod;
base = base * base % mod;
y >>= 1;
}
return ans;
}
ll gauss(int 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 d = k[i][i] / k[j][i];
for(int o = 1; o <= n - 1; o++){
//注意取模
k[i][o] = (k[i][o] - d * k[j][o] + mod) % mod;
}
swap(k[i], k[j]);
res = -res;
}
}
//注意取模
res = res * k[i][i] % mod;
}
return (res + mod) % mod);
}
int main(){
#ifdef DEBUG
freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
memset(k, 0, sizeof(k));
for(int i = 1; i <= m; i++) scanf("%d%d%lld", &edge[i].u, &edge[i].v, &edge[i].w);
for(int i = 1; i <= m; i++){
int x = edge[i].u;
int y = edge[i].v;
// k是基尔霍夫矩阵。
k[x][x]++;
k[y][y]++;
k[x][y]--;
k[y][x]--;
}
sum = gauss(n);
ll ans = 0;
// 遍历每一位
for(int i = 0; i < 32; i++){
memset(k, 0, sizeof(k));
for(int j = 1; j <= m; j++){
if(edge[j].w & (1LL << i)){
int x = edge[j].u;
int y = edge[j].v;
k[x][x]++;
k[y][y]++;
k[x][y]--;
k[y][x]--;
}
}
ll tmp = gauss(n);
ans = (ans + tmp * (1ll << i) % mod * qpow(sum, mod - 2) % mod) % mod;
}
printf("%lld\n", ans);
}
return 0;
}