首先定义了一棵生成树的重量:这棵树所有边的按位与(AND)
给出一个无向连通图,随机挑选一个生成树,问这个生成树的重量的期望是多少。
数学期望的性质: E ( X + Y ) = E ( X ) + E ( Y ) E(X + Y) = E(X) + E(Y) E(X+Y)=E(X)+E(Y)
本题中给出的生成树的重量的定义:所有边的边权的按位与,我们可以拆开,按位来考虑。
利用数学期望的性质,可以得到下面的式子:
E ( a n s ) = E ( x 31 ∗ 2 31 + x 30 ∗ 2 30 + . . . + x 0 ∗ 2 0 ) = E ( x 31 ∗ 2 31 ) + E ( x 30 ∗ 2 30 ) + . . . + E ( x 0 ∗ 2 0 ) E(ans) = E(x_{31}*2^{31} + x_{30}*2^{30} + ... + x_{0}*2^{0}) = E(x_{31}*2^{31}) + E(x_{30}*2^{30}) + ... + E(x_{0}*2^{0}) E(ans)=E(x31∗231+x30∗230+...+x0∗20)=E(x31∗231)+E(x30∗230)+...+E(x0∗20)
下面我们只要考虑每一位就可以了。
第 i i i位要怎样才能是1呢, 要所有边的边权的第 i i i位都是1。
我只要把所有的第 i i i位是 1 1 1的边找出来,然后计算一下这些边能够构成多少个生成树。那这个生成树的数量就是重量第 i i i位为 1 1 1的所有生成树。设这个数量为 n u m num num,这张图总生成树数量为 s u m sum sum。
那期望就能算出来, E ( x i ∗ 2 i ) = 2 i ∗ n u m s u m E(x_{i}*2^{i}) = 2^{i} * {{num} \over {sum}} E(xi∗2i)=2i∗sumnum。
依次计算32位,最后相加就行了。
然后生成树个数的计算,使用矩阵树定理就能计算出来。
求出基尔霍夫矩阵之后,只要求它的一个代数余子式即可。下面的模板是求 a n n a_{nn} ann的代数余子式,即 ( − 1 ) n + n ∗ M n n (-1)^{n + n}*M_{nn} (−1)n+n∗Mnn,因为 n + n n + n n+n一定是偶数,所以也就是求去掉第 n n n行,第 n n n列之后的 n − 1 n-1 n−1阶方阵的行列式。
行列式是转换成上三角之后,对角线元素乘积直接得到。
另:高斯消元部分模板是采用辗转相除的方式消去下面的行,因为答案要取模。与求GCD的方式类似,不断相除,总有一个会变成 0 0 0。
高斯消元过程还涉及到交换两行的操作,根据行列式的性质,交换两行,行列式变为原来的负数。
/*
* @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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#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 <
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;
}