给定一张$n$个点$m$条边的图。对于图中的所有生成树$T$(假设它的边为$e_{1\sim n-1}$),求$\sum_T(\sum_{i=1}^{n-1}w_{e_i}\times \gcd(w_{e_1},w_{e_2},...,w_{e_{n-1}}))$。

点此看题面

  • 给定一张\(n\)个点\(m\)条边的图。
  • 对于图中的所有生成树\(T\)(假设它的边为\(e_{1\sim n-1}\)),求\(\sum_T(\sum_{i=1}^{n-1}w_{e_i}\times \gcd(w_{e_1},w_{e_2},...,w_{e_{n-1}}))\)
  • \(n\le30,m\le\frac{n(n-1)}2,\forall w\le152501\)

欧拉函数

看到\(\gcd\)显然一眼想到莫比乌斯反演。

然而此题其实并不用这么麻烦,我们直接考虑欧拉函数的性质(\(\sum_{d|n}\phi(d)=n\)),就能更加方便快捷地解决这道题。

我们枚举\(d=\gcd(w_{e_1},w_{e_2},...,w_{e_{n-1}})\)得到答案式相当于:

\[\sum_{d=1}^V \phi(d)\times\sum_{T(d|w_{e_1},d|w_{e_2},...,d|w_{e_{n-1}})}(\sum_{i=1}^{n-1}w_{e_i}) \]

矩阵树定理

这里给出矩阵树定理的完整版吧:对于一张带权图,按边权建度数矩阵和邻接矩阵,则用矩阵树定理可以求出所有生成树边权之积的和。

对于这道题自然想到利用矩阵树定理求解,然而定理能求的是边权之积的和,我们要求的是边权之和的和。

但实际上有一个和与积的经典转化,我们把一条边权为\(w\)的边看作一个二项式\(1+wx\),然后发现\((1+w_1x)\cdot(1+w_2x)\)的一次项系数恰好是\(w_1+w_2\)

所以我们只需要把边权设为二项式即可,具体运算过程中加、减、乘显然都是非常容易的,除的话就考虑\((a+bx)\cdot(a-bx)=a^2-b^2x^2\),由于我们只要二项式,故可以忽略二次项得到\((a+bx)\cdot(a-bx)\equiv a^2(mod\ x^2)\),所以\((a+bx)^{-1}=\frac{a-bx}{a^2}\),就实现了除法向乘法的转化。

代码:\(O(n^3d(n)logn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 30
#define V 152501
#define SV 400
#define X 998244353
using namespace std;
int n,m;I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
struct LinearSieve
{
	int Pt,P[V+5],phi[V+5];I LinearSieve()//线性筛预处理φ
	{
		phi[1]=1;for(RI i=2,j;i<=V;++i) for(!P[i]&&(phi[P[++Pt]=i]=i-1),j=1;j<=Pt&&i*P[j]<=V;++j)
			if(P[i*P[j]]=1,i%P[j]) phi[i*P[j]]=phi[i]*(P[j]-1);else {phi[i*P[j]]=phi[i]*P[j];break;}
	}
}L;
struct Data
{
	int x,y;I Data(CI a=0,CI b=0):x(a),y(b){}
	I Data operator + (Con Data& o) Con {return Data((x+o.x)%X,(y+o.y)%X);}
	I Data operator - (Con Data& o) Con {return Data((x-o.x+X)%X,(y-o.y+X)%X);}
	I Data operator * (Con Data& o) Con {return Data(1LL*x*o.x%X,(1LL*x*o.y+1LL*y*o.x)%X);}
	I Data operator / (Con Data& o) Con {return *this*Data(o.x,X-o.y)*QP(1LL*o.x*o.x%X,X-2);}//除法转乘法
}a[N+5][N+5];
struct line {int x,y,v;I line(CI a=0,CI b=0,CI c=0):x(a),y(b),v(c){}};vector<line> g[V+5];
I int Calc(CI id)//计算id倍数的生成树边权之和之和
{
	if(g[id].empty()) return 0;RI i,j;for(i=1;i<=n;++i) for(j=1;j<=n;++j) a[i][j]=Data();//清空
	for(vector<line>::iterator it=g[id].begin();it!=g[id].end();++it)//初始化度数矩阵和邻接矩阵
		a[it->x][it->x]=a[it->x][it->x]+Data(1,it->v),a[it->y][it->y]=a[it->y][it->y]+Data(1,it->v),
		a[it->x][it->y]=a[it->x][it->y]-Data(1,it->v),a[it->y][it->x]=a[it->y][it->x]-Data(1,it->v);
	RI k,op=1;Data t;for(i=1;i^n;++i)//求行列式
	{
		if(!a[i][i].x&&!a[i][i].y)//如果这一项为空
		{
			for(op=X-op,j=i+1;j<=n&&!a[j][i].x&&!a[j][i].y;++j);if(j>n) return 0;//找一行替换
			for(k=1;k<=n;++k) swap(a[i][k],a[j][k]);//交换
		}
		for(j=i+1;j^n;++j) for(t=(Data()-a[j][i])/a[i][i],k=i;k<=n;++k) a[j][k]=a[j][k]+t*a[i][k];//消元
	}
	for(t=op,i=1;i^n;++i) t=t*a[i][i];return t.y;//求出对角线元素的乘积
}
int main()
{
	RI i,j,x,y,z;for(scanf("%d%d",&n,&m),i=1;i<=m;++i) for(scanf("%d%d%d",&x,&y,&z),
		j=1;j*j<=z;++j) !(z%j)&&(g[j].push_back(line(x,y,z)),j^(z/j)&&(g[z/j].push_back(line(x,y,z)),0));//扔到每个因数对应的vector中
	RI t=0;for(i=1;i<=V;++i) t=(1LL*L.phi[i]*Calc(i)+t)%X;return printf("%d\n",t),0;//枚举公约数统计答案
}
败得义无反顾,弱得一无是处