模板:矩阵树定理

60 篇文章 0 订阅
57 篇文章 0 订阅

所谓矩阵树定理,就是用矩阵解决树问题的定理。

(逃)

前言

神奇科技。
之前一直没有写博客,觉得还是写一发比较好。
证明什么的是不可能会的
背下来背下来!

解析

无向图

定义一个矩阵 A A A
A i , i = d i A i , j = − # ( i , j ) ( i ≠ j ) A_{i,i}=d_i\\A_{i,j}=-\#(i,j)(i\ne j) Ai,i=diAi,j=#(i,j)(i=j)
其中 # ( i , j ) \#(i,j) #(i,j) 表示这条边的数量。
求出这个矩阵的任意一个余子式即可。

有向图

根向树

定义一个矩阵 A A A
A i , i = d i + A i , j = − # ( i , j ) ( i ≠ j ) A_{i,i}=d_i^+\\A_{i,j}=-\#(i,j)(i\ne j) Ai,i=di+Ai,j=#(i,j)(i=j)
去掉第 i i i 行得到的余子式就是以 i i i 为根的答案。

叶向树

定义一个矩阵 A A A
A i , i = d i − A i , j = − # ( i , j ) ( i ≠ j ) A_{i,i}=d_i^-\\A_{i,j}=-\#(i,j)(i\ne j) Ai,i=diAi,j=#(i,j)(i=j)
唯一的区别就是主对角线从出度变成了入度(为什么呢?因为根的思想太陈旧,已经out

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=305;
const int mod=1e9+7;

inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}

inline ll ksm(ll x,ll k){
	ll res(1);
	while(k){
		if(k&1) res=res*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return res;
}

int n,m;
ll a[N][N];
ll calc(int n){
	ll ans(1);
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			if(a[j][i]){
				if(j!=i) swap(a[i],a[j]);
				break;
			}
		}
		for(int j=i+1;j<=n;j++){
			ll d=a[j][i]*ksm(a[i][i],mod-2)%mod;
			for(int k=i;k<=n;k++) a[j][k]=(a[j][k]+mod-a[i][k]*d%mod)%mod;
		}
	}
	for(int i=1;i<=n;i++) ans=ans*a[i][i]%mod;
	return ans;
}
void work0(){
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),w=read();
		if(x==y) continue;
		(a[x][x]+=w)%=mod;
		(a[y][y]+=w)%=mod;
		(a[x][y]+=mod-w)%=mod;
		(a[y][x]+=mod-w)%=mod;
	}
	printf("%lld\n",calc(n-1));
}
void work1(){
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),w=read();
		if(x==y) continue;
		(a[y][y]+=w)%=mod;
		(a[x][y]+=mod-w)%=mod;
	}
	for(int i=1;i<n;i++){
		for(int j=1;j<n;j++) a[i][j]=a[i+1][j+1];
	}
	printf("%lld\n",calc(n-1));
}

signed main(){
#ifndef ONLINE_JUDGE
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
#endif
	n=read();m=read();int op=read();
	if(op==0) work0();
	else work1();
	return 0;
}
/*
3
2 1 1
1 1
*/


带权图

如果一棵生成树的权值定义为边权乘积,直接把边理解成边权条1权边在对应的位置加边权即可。

如果一棵生成树的权值定义为边权加和,需要在矩阵内维护一个一次函数,最终在 m o d    x 2 \mod x^2 modx2 意义下求出的行列式的一次项就是答案。
因为这就相当于单独考虑一条边的边权,看它能加入多少棵生成树中。

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=35;
const int M=1050;
const int S=2e5+100;
const int inf=1e9;
const int mod=998244353;

inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}

inline ll ksm(ll x,ll k){
	ll res(1);
	while(k){
		if(k&1) res=res*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return res;
}

int n,m;
int mx;
int prime[S],vis[S],tot;
ll mu[S];
void init(int n){
	mu[1]=1;
	for(int i=2;i<=n;i++){
		if(!vis[i]){
			prime[++tot]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=tot&&prime[j]<=n/i;j++){
			int now=prime[j];
			vis[i*now]=1;
			if(i%now==0){
				mu[i*now]=0;break;
			}
			mu[i*now]=-mu[i];
		}
	}
	for(int i=1;i<=n;i++) mu[i]=(mu[i]+mod)%mod;
	return;
}

struct node{
	ll a,b;
};
node operator + (const node x,const node y){return (node){(x.a+y.a)%mod,(x.b+y.b)%mod};}
node operator - (const node x,const node y){return (node){(x.a+mod-y.a)%mod,(x.b+mod-y.b)%mod};}
node operator * (const node x,const node y){return (node){(x.a*y.b+x.b*y.a)%mod,x.b*y.b%mod};}
node operator / (const node x,const node y){
	ll niv=ksm(y.b,mod-2);
	return (node){(x.a*y.b-x.b*y.a%mod+mod)%mod*niv%mod*niv%mod,x.b*niv%mod};
}
void operator += (node &x,const node y){x=x+y;}
void operator -= (node &x,const node y){x=x-y;}
void operator *= (node &x,const node y){x=x*y;}
void operator /= (node &x,const node y){x=x/y;}

int u[M],v[M],w[M];
node a[N][N];
ll g[S],f[S];
void print(int n){
	puts("\n------\n");
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) printf("(%lld %lld)  ",a[i][j].a,a[i][j].b);
		puts("");
	}
}
ll calc(int n){
	node ans=(node){0,1};
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			node d=a[j][i]/a[i][i];
			for(int k=i;k<=n;k++) a[j][k]-=(a[i][k]*d);
		}
	}
	for(int i=1;i<=n;i++){
		//printf("  i=%d a=%lld ans=%lld\n",i,a[i][i],ans[i])
		ans=ans*a[i][i];
	}
	return ans.a;
}
void work(int x){
	memset(a,0,sizeof(a));
	int cnt(0);
	for(int i=1;i<=m;i++){
		if(w[i]%x) continue;
		a[u[i]][u[i]]+=(node){w[i],1};
		a[v[i]][v[i]]+=(node){w[i],1};
		a[u[i]][v[i]]-=(node){w[i],1};
		a[v[i]][u[i]]-=(node){w[i],1};
		cnt++;
	}
	if(cnt<n-1) return;
	//printf("\n---x=%d\n",x);
	//print(n);
	g[x]=calc(n-1);
	//printf("ans=%lld\n",g[x]);
	return;
}

signed main() {
#ifndef ONLINE_JUDGE
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
#endif
	n=read();m=read();
	for(int i=1;i<=m;i++){
		u[i]=read();v[i]=read();w[i]=read();
		mx=max(mx,w[i]);
	}
	init(mx);
	for(int x=1;x<=mx;x++) work(x);
	ll ans(0);
	for(int i=1;i<=mx;i++){
		for(int j=1;j*i<=mx;j++) (f[i]+=g[i*j]*mu[j])%=mod;
		(ans+=i*f[i])%=mod;
	}
	printf("%lld\n",ans);
	return 0;
}
/*
3
2 1 1
1 1
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值