1. 程式人生 > >jzoj5899 【NOIP2018模擬10.6】資源運輸 (矩陣樹定理)

jzoj5899 【NOIP2018模擬10.6】資源運輸 (矩陣樹定理)

描述

n<=300,給定有權邊,求生成樹大小和所有生成樹邊權乘積和。

要點

  • 基爾霍夫矩陣: c [ i ] [ i ]
    c[i][i]
    為點i的度數, c [ i ] [ j ]
    = ( i , j
    ) c[i][j]=-(i,j之間邊數)
  • 行列式:列舉每一個1…n的排列,將每行對應的列乘起來, 再乘上 ( 1 ) (-1)^{逆序對個數} 之和。
  • PTY式:任意選取i,去掉第i行第i列後的行列式。
  • 基爾霍夫矩陣的餘子式就是生成樹個數。
  • 有邊權視作多條重邊即可。

行列式 O ( n 3 ) O(n^3) 求法:

  • (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;
}