luoguP6624 [省选联考 2020 A 卷] 作业题(莫比乌斯反演,矩阵树定理)

luoguP6624 [省选联考 2020 A 卷] 作业题(莫比乌斯反演,矩阵树定理)

Luogu

题外话:

Day2一题没切。

我是傻逼。

题解时间

某种意义上说刻在DNA里的柿子,大概是很多人学莫反做的第一题的套路。

$ \phi \cdot 1 = id $ 。

然后直接转化:

\[\begin{aligned} & \sum_{T} ( ( \sum w_{e_i} ) * gcd( w_{e_i} ) ) \\ = & \sum_{T} ( ( \sum w_{e_i} ) * \sum_{d|gcd( w_{e_i} )} \phi(d) ) \\ = & \sum_{d} \phi(d) \sum_{T:d|gcd( w_{e_i} )} ( \sum w_{e_i} ) \end{aligned} \]

然后对于求出边权和,简单的想法是对于每条边求有多少含这条边的树。

这时就可以想到矩阵树定理了。但怎么对于每个边求呢?

简单思考发现矩阵元素变成 $ (a+bx) $ 的形式就可以解决。

一条边加的元素是 $ (1+wx) $ ,答案就是求得结果的一次项系数。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long lint;
template<typename TP>inline void read(TP &tar)
{
	TP ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){ret=ret*10+(ch-'0');ch=getchar();}
	tar=ret*f;
}
namespace RKK
{
const int N=40,M=500;
const int mo=998244353;
int add(const int &a,const int &b){return a+b>=mo?a+b-mo:a+b;}
void doadd(int &a,const int &b){if((a+=b)>=mo) a-=mo;}
int fpow(int a,int p){int ret=1;while(p){if(p&1)ret=1ll*ret*a%mo;a=1ll*a*a%mo,p>>=1;}return ret;}
int pr[160011],pc,phi[160011];bool npr[160011];
void sieve()
{
	phi[1]=1;for(int i=2;i<=160000;i++)
	{
		if(!npr[i]) pr[++pc]=i,phi[i]=i-1;
		for(int j=1;j<=pc&&i*pr[j]<=160000;j++)
		{
			npr[i*pr[j]]=1;
			if(i%pr[j]==0){phi[i*pr[j]]=phi[i]*pr[j];break;}
			else phi[i*pr[j]]=phi[i]*(pr[j]-1);
		}
	}
}
int n,m,ans,ex[M],ey[M],ew[M];
struct pat
{
	int x,y;
	pat(const int &x=0,const int &y=0):x(x),y(y){}
	pat operator+(const pat &p)const{return pat(add(x,p.x),add(y,p.y));}
	pat operator-(const pat &p)const{return pat(add(x,mo-p.x),add(y,mo-p.y));}
	pat operator*(const pat &p)const{return pat(1ll*x*p.x%mo,(1ll*x*p.y+1ll*p.x*y)%mo);}
	pat operator/(const pat &p)const
	{
		int iv=fpow(p.x,mo-2);
		return pat(1ll*x*iv%mo,1ll*add(1ll*y*p.x%mo,mo-1ll*x*p.y%mo)*iv%mo*iv%mo);
	}
};
pat a[N][N];
pat mtree()
{
	pat ret=pat(1,0);bool rev=0;
	for(int l=1,e;l<n;l++)
	{
		for(e=l;e<n;e++)if(a[e][l].x) break;if(e==n) return pat(0,0);
		if(e!=l){rev^=1;for(int j=1;j<n;j++) swap(a[l][j],a[e][j]);}
		pat k=pat(1,0)/a[l][l];
		for(int i=l+1;i<n;i++)
		{
			pat g=k*a[i][l];
			for(int j=l;j<n;j++) a[i][j]=a[i][j]-g*a[l][j];
		}
		ret=ret*a[l][l];
	}
	if(rev) ret=pat(0,0)-ret;return ret;
}
int getans(int p)
{
	memset(a,0,sizeof(a));
	for(int i=1;i<=m;i++)if(ew[i]%p==0)
	{
		a[ex[i]][ey[i]]=a[ex[i]][ey[i]]-pat(1,ew[i]);
		a[ey[i]][ex[i]]=a[ey[i]][ex[i]]-pat(1,ew[i]);
		a[ex[i]][ex[i]]=a[ex[i]][ex[i]]+pat(1,ew[i]);
		a[ey[i]][ey[i]]=a[ey[i]][ey[i]]+pat(1,ew[i]);
	}
	return mtree().y;
}
int ct[160011];


int main()
{
	sieve();read(n),read(m);
	for(int i=1;i<=m;i++)
	{
		read(ex[i]),read(ey[i]),read(ew[i]);
		for(int j=1;j*j<=ew[i];j++)if(ew[i]%j==0){ct[j]++;if(ew[i]/j!=j) ct[ew[i]/j]++;}
	}
	for(int i=1;i<=160000;i++)if(ct[i]>=n-1) ans=(ans+1ll*phi[i]*getans(i))%mo;
	printf("%d\n",ans);
	return 0;
}
}
int main(){return RKK::main();}
posted @ 2020-06-28 18:09  RikukiIX  阅读(174)  评论(0编辑  收藏  举报