2020 HDU 多校第六场 1010 Expectation 【期望 、矩阵树定理】

题意:

首先定义了一棵生成树的重量:这棵树所有边的按位与(AND

给出一个无向连通图,随机挑选一个生成树,问这个生成树的重量的期望是多少。

题解:

做法:

数学期望的性质: 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})

下面我们只要考虑每一位就可以了。

i i 位要怎样才能是1呢, 要所有边的边权的第 i i 位都是1。

我只要把所有的第 i i 位是 1 1 的边找出来,然后计算一下这些边能够构成多少个生成树。那这个生成树的数量就是重量第 i i 位为 1 1 的所有生成树。设这个数量为 n u m num ,这张图总生成树数量为 s u m sum

那期望就能算出来, E ( x i 2 i ) = 2 i n u m s u m E(x_{i}*2^{i}) = 2^{i} * {{num} \over {sum}}

依次计算32位,最后相加就行了。

然后生成树个数的计算,使用矩阵树定理就能计算出来。

矩阵树定理:

矩阵树定理参考博客地址
百度百科:
在这里插入图片描述

求出基尔霍夫矩阵之后,只要求它的一个代数余子式即可。下面的模板是求 a n n a_{nn} 的代数余子式,即 ( 1 ) n + n M n n (-1)^{n + n}*M_{nn} ,因为 n + n n + n 一定是偶数,所以也就是求去掉第 n n 行,第 n n 列之后的 n 1 n-1 阶方阵的行列式。

行列式是转换成上三角之后,对角线元素乘积直接得到。

:高斯消元部分模板是采用辗转相除的方式消去下面的行,因为答案要取模。与求GCD的方式类似,不断相除,总有一个会变成 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<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;
}

猜你喜欢

转载自blog.csdn.net/qq_44607936/article/details/107871680