题解 [联合省选 2020 A] 作业题(LOJ #3304 / 洛谷 P6624)【矩阵树定理】

题目链接:洛谷 P6624 / LOJ #3304

题意

给定无向带权简单图,其生成树的价值定义为树内边权之和乘以其 gcd ⁡ \gcd gcd,求其所有生成树的价值和。 n ≤ 30 n\leq 30 n30 w (边权) ≤ 152501 w{\tiny\text{(边权)}} \leq 152501 w(边权)152501

题解

考虑当 gcd ⁡ \gcd gcd 确定时所有生成树的边权和之和,然后把它转化为 gcd ⁡ \gcd gcd d d d 的倍数时所有生成树的边权和之和再容斥一下。

先枚举 d d d,把所有 d d d 的倍数的边提出来并判断其连通性,如果不连通则 d d d 的倍数都不连通。不连通的则没有计算的必要。

接着考虑如何计算生成树的权值和。对于权值为 w w w 的边,我们算基尔霍夫矩阵时将它的边权当做 1 + w x 1+wx 1+wx,矩阵树定理算完行列式后一次项系数即为答案(对于一条权值为 w w w 的边,它对答案的贡献就是其权值乘以固定这条边后的生成树数量,也就是 w x wx wx 乘以一个常数项)。

a + b x a+bx a+bx 的逆元可以考虑 1 + c x 1+cx 1+cx 的逆元是 1 − c x 1-cx 1cx,然后就好算了。

还有个玄学剪枝就是所有选出来的边的 gcd ⁡ \gcd gcd 不为 d d d 的话显然没有计算的意义。

代码:

/**********
Author: WLBKR5
Problem: loj 3304, luogu 6624 
Name: ×÷ÒµÌâ 
Source: ÁªºÏÊ¡Ñ¡ 2020 A¾í 
Algorithm: ¾ØÕóÊ÷¶¨Àí 
Date: 2020/06/24
Statue: accepted
Submission: loj.ac/submission/844872, www.luogu.com.cn/record/34630205
**********/
#include<bits/stdc++.h>
using namespace std;
int getint(){
	int ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	}
	return ans;
}
const int N=32,W=1.6e5,mod=998244353;
int w[N][N],n;

inline int qpow(int x,int y){
	int ans=1;
	while(y){
		if(y&1)ans=ans*1ll*x%mod;
		x=x*1ll*x%mod;
		y>>=1;
	}
	return ans;
}

struct F{
	int a,b;//a+bx
	F():a(0),b(0){}
	F(int x,int y=0):a(x),b(y){if(a>=mod)a-=mod;if(b>=mod)b-=mod;}
	operator bool(){ return a||b; }
};
inline F operator* (const F &a,const F &b){
	return F(a.a*1ll*b.a%mod,(a.a*1ll*b.b+a.b*1ll*b.a)%mod);
}
inline F operator+ (const F &a,const F &b){ return F((a.a+b.a),(a.b+b.b)); }
inline F operator- (const F &a,const F &b){ return F((a.a+mod-b.a),(a.b+mod-b.b)); }
inline F inv(const F &a){
	int i=qpow(a.a,mod-2);
	F b(a*i);
	return F(b.a,mod-b.b)*F(i);
}
ostream& operator<< (ostream &out,const F &a){
	out<<a.a<<"+"<<a.b<<"x";
	return out;
}

F a[N][N];
int gauss(){
	F ans(1);
	for(register int i=1;i<n;i++){
		if(!a[i][i])return 0;
		ans=ans*a[i][i];
		F v=inv(a[i][i]);
		for(register int j=i+1;j<n;j++){
			if(!a[j][i])continue;
			F t=a[j][i]*v;
			for(register int k=i;k<=n;k++)a[j][k]=a[j][k]-a[i][k]*t;
		}
	}
	return ans.b;
}

int res[W];

bool ok[W];
int fa[N];
inline int _(int x){ return fa[x]==x?x:fa[x]=_(fa[x]); }

inline int gcd(int x,int y){
	return x?gcd(y%x,x):y;
}

int main(){
	n=getint();
	int m=getint(),maxw=0;
	for(int i=0;i<m;i++){
		int x=getint(),y=getint(),z=getint();
		w[x][y]=w[y][x]=z;maxw=max(maxw,z);
	}
	int ans=0;
	memset(ok,1,sizeof(bool)*maxw+10);
	for(int d=1;d<=maxw;d++){
		if(!ok[d])continue;
		for(int i=1;i<=n;i++)fa[i]=i;
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++)
				if(w[i][j]&&w[i][j]%d==0)
					fa[_(i)]=_(j);
		for(int i=2;i<=n;i++){
			if(_(i)!=_(1)){
				ok[d]=0;
				break;
			}
		}
		gg:;
		if(!ok[d])for(int i=d+d;i<=maxw;i+=d)ok[i]=0;
	}
	for(int d=maxw;d>=1;d--){
		if(!ok[d])continue;
		int g=0;
		for(int i=1;i<=n;i++)for(int j=i;j<=n;j++){
			if(w[i][j]&&w[i][j]%d==0)g=gcd(g,w[i][j]);
			a[j][i]=a[i][j]=F();
		}
		if(g>d)continue;
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++)
				if(w[i][j]&&w[i][j]%d==0){
					a[j][i]=a[i][j]=F(mod-1,mod-w[i][j]);
					a[i][i]=a[i][i]+F(1,w[i][j]);
					a[j][j]=a[j][j]+F(1,w[i][j]);
				}
		res[d]=gauss();
		for(int i=d+d;i<=maxw;i+=d)res[d]=(res[d]+mod-res[i])%mod;
		ans=(ans+res[d]*1ll*d)%mod;
	}
	cout<<ans;
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值