[HDU-6836] Expectation【矩阵树定理模板】
题意
给定一个无向图,定义一颗生成树的权重是其所有边权的与,问任意选一颗生成树的权重期望。(每个生成树选择概率相同)
思路
首先,因为等概率,所以答案就是 所 有 生 成 树 的 权 重 和 生 成 树 个 数 \frac{所有生成树的权重和}{生成树个数} 生成树个数所有生成树的权重和 。矩阵树定理可以 O ( n 3 ) O(n^3) O(n3) 求出一个无向图的生成树个数,也就是分母;其次对于边权的第 i i i 位,其在生成树的最终权重中为 1 1 1 的唯一条件是该生成树所有边的第 i i i 位都是 1 1 1 。所以我们枚举边权的每一位,只加入所有当前位为1的边建图,这样形成的无向图中的所有生成树都能保证树的权重该位为1。那么我们数出这个无向图的生成树个数 n u m num num,其对于分子的贡献就是 n u m ∗ 2 i num * 2 ^ i num∗2i,这里的 i i i 指当前是第 i i i 位。具体实现见代码。hhe
这题有个坑点,就是会有重边,所以在给矩阵赋值的时候不能只赋值成-1,要改成每次减1。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e2 + 10;
const int M = 1e4 + 10;
const int mod = 998244353;
/*----------------矩阵树模板------------------*/
int n, m;
struct edge
{
int u, v;
ll cost;
};
vector<edge> es;
ll det(ll a[][N])
{
ll res = 1;
for(int i = 2; i <= n; i++)
{
for(int j = i + 1; j <= n; j++)
{
while(a[j][i])
{
ll t = a[i][i] / a[j][i];
for(int k = i; k <= n; k++)
a[i][k] = (a[i][k] - (a[j][k] * t) % mod + mod) % mod;
for(int k = i; k <= n; k++)
swap(a[i][k], a[j][k]);
res = -res;
}
}
if(!a[i][i])
return 0;
res = res * a[i][i] % mod;
}
return (res + mod) % mod;
}
ll solve()
{
ll g[N][N] = {0};
ll d[N] = {0};
for(auto e : es)
{
int u = e.u;
int v = e.v;
g[u][v]--; //这里如果写成g[u][v] = -1, 就会在有重边的数据上WA掉
g[v][u]--;
d[u]++;
d[v]++;
}
for(int i = 1; i <= n; i++)
g[i][i] = d[i];
return det(g) % mod;
}
/*-------------------------------------------*/
/*---------矩阵树模板变体:按位枚举建图---------*/
ll solve(int pos)
{
ll g[N][N] = {0};
ll d[N] = {0};
for(auto e : es)
{
if(((e.cost >> pos) & 1) == 0)
continue;
int u = e.u;
int v = e.v;
g[u][v]--; //这里如果写成g[u][v] = -1, 就会在有重边的数据上WA掉
g[v][u]--;
d[u]++;
d[v]++;
}
for(int i = 1; i <= n; i++)
g[i][i] = d[i];
return det(g) % mod;
}
/*-------------------------------------------*/
ll qpow(ll x, ll n)
{
x %= mod;
ll res = 1;
while(n)
{
if(n & 1)
res = (res * x) % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n >> m;
es.resize(m);
for(int i = 0, u, v, w; i < m; i++)
{
cin >> u >> v >> w;
es[i] = {u, v, (ll)w};
}
ll sum = solve();//总生成树个数,直接用矩阵树模板求
ll inv_sum = qpow(sum, mod - 2);
ll res = 0;
for(int i = 0; i < 31; i++) //建30次图,每次只连这一位上是1的边
{
int num = solve(i) % mod * (1LL << i) % mod;//贡献是(个数*2^i)
res = (res + num) % mod;
}
res = (res * inv_sum) % mod;
cout << res << endl;
}
return 0;
};