jzoj5899 【NOIP2018模拟10.6】资源运输 (矩阵树定理)

版权声明:未经作者本人允许禁止转载。 https://blog.csdn.net/jokerwyt/article/details/82954212

描述

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

猜你喜欢

转载自blog.csdn.net/jokerwyt/article/details/82954212
今日推荐