描述
n<=300,给定有权边,求生成树大小和所有生成树边权乘积和。
要点
- 基尔霍夫矩阵: c [ i ] [ i ] c[i][i] c[i][i]为点i的度数, c [ i ] [ j ] = − ( i , j 之 间 边 数 ) c[i][j]=-(i,j之间边数) c[i][j]=−(i,j之间边数)
- 行列式:枚举每一个1…n的排列,将每行对应的列乘起来, 再乘上 ( − 1 ) 逆 序 对 个 数 (-1)^{逆序对个数} (−1)逆序对个数之和。
- PTY式:任意选取i,去掉第i行第i列后的行列式。
- 基尔霍夫矩阵的余子式就是生成树个数。
- 有边权视作多条重边即可。
行列式 O ( n 3 ) O(n^3) O(n3)求法:
- (1)上三角矩阵的行列式就是对角线相乘。
- (2)为了变成上三角矩阵,高斯消元。
- (3)高斯消元所需操作:
- 一行与另一行交换,行列式取相反数。
- 一行乘a,行列式乘a。
- 一行减去另一行的倍数:不变。
- 最好记的性质|AB|=|A||B|,记住这个上面的都可以通过矩阵乘法看出来。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 310, mo = 998244353;
int n,m;
struct edge{
int w,x,y;
} e[1010];
ll cnt,sum,f[N][N],g[N][N];
ll ksm(ll x,ll y) {
ll ret=1; for (;y;y>>=1){
if (y&1) ret = ret * x % mo;
x = x * x % mo;
}
return ret;
}
void print(ll d[N][N]) {
for (int i = 1; i < n; i++) {
for (int j = 1; j < n; j++) printf("%d ",d[i][j]);
printf("\n");
}
}
ll solve(ll d[N][N]) {
int xs = 1;
for (int i = 1; i < n; i++) {
for (int j = i; j < n; j++) {
if (d[j][i]) {
if (i!=j) xs = -xs;
for (int z = 1; z < n; z++)
swap(d[i][z],d[j][z]);
break;
}
}
for (int j = i+1; j < n; j++) if (d[j][i]) {
xs = xs * d[i][i] % mo; ll r = d[j][i];
for (int z = 1; z < n; z++)
d[j][z] = (d[j][z] * d[i][i] - d[i][z] * r) % mo;
}
}
xs = ksm(xs, mo-2);
for (int i = 1; i < n; i++) xs = xs * d[i][i] % mo;
return (xs + mo) % mo;
}
int main() {
freopen("avg.in","r",stdin);
// freopen("avg.out","w",stdout);
cin>>n>>m;
for (int i = 1; i <= m; i++) {
int u,v,w;
scanf("%d %d %d",&e[i].x,&e[i].y,&e[i].w);
u = e[i].x, v = e[i].y;
f[u][u]++;
f[v][v]++;
f[u][v]=f[v][u]=-1;
g[u][u]=(g[u][u]+e[i].w)%mo;
g[v][v]=(g[v][v]+e[i].w)%mo;
g[u][v]=g[v][u]=-e[i].w;
}
ll cnt = solve(f), sum = solve(g);
cout<<(sum*ksm(cnt,mo-2)%mo+mo)%mo<<endl;
}