jzoj5899 【NOIP2018模擬10.6】資源運輸 (矩陣樹定理)
阿新 • • 發佈:2018-12-02
描述
n<=300,給定有權邊,求生成樹大小和所有生成樹邊權乘積和。
要點
- 基爾霍夫矩陣: 為點i的度數,
- 行列式:列舉每一個1…n的排列,將每行對應的列乘起來, 再乘上 之和。
- PTY式:任意選取i,去掉第i行第i列後的行列式。
- 基爾霍夫矩陣的餘子式就是生成樹個數。
- 有邊權視作多條重邊即可。
行列式 求法:
- (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;
}