【洛谷6624】[省选联考 2020 A 卷] 作业题(欧拉函数+矩阵树定理)

点此看题面

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

欧拉函数

看到(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) imessum_{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
",t),0;//枚举公约数统计答案
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6624.html