洛谷 P6624 - [省选联考 2020 A 卷] 作业题(矩阵树定理+简单数论)

题面传送门

首先看到 \(\gcd\) 可以套路地往数论方向想,我们记 \(f_i\) 为满足边权的 \(\gcd\)\(i\) 的倍数的所有生成树的权值之和,\(g_i\) 为边权的 \(\gcd\) 恰好为 \(i\) 的所有生成树的权值之和,那么显然 \(f_i=\sum\limits_{i\mid j}g_j\),莫反一下可得 \(g_i=\sum\limits_{i\mid j}f_i\mu(\dfrac{j}{i})\),因此我们只需求出 \(f_i\) 即可求出 \(g_i\) 及最终的答案。

接下来考虑怎样求 \(f_i\),我们考虑原图 \(G\) 中边权为 \(i\) 的倍数的边组成的子图 \(G'\),那么 \(f_i\) 等价于 \(G'\) 所有生成树的边权之和,看到生成树计数这类题目我们很自然地可以想到 Matrix-Tree 定理。不过一般来说 Matrix-Tree 定理解决的是求所有生成树边权乘积之和,而此题要求的是所有生成树边权之和,这里有一个烂大街但我觉得挺妙的套路,我们将所有边的边权看作一个一次函数 \(y=1+wx\),那么显然这个边权和就是生成树上所有一次函数乘积的一次项,这显然是可以 Matrix-Tree 的。不过问题又来了,再求矩阵行列式的过程中涉及加减乘除运算,怎样定义这些运算呢?首先由于我们只关心最终函数的一次项,因此我们可以将所有运算放在 \(\bmod x^2\) 意义下进行,加减法就直接加就行了,乘法就稍微用下分配律,\((a+bx)(c+dx)=ac+(ad+bc)x\),除法稍微有点麻烦,不过学过多项式的比较好理解,首先 \(\dfrac{a+bx}{c+dx}\) 的常数项必须是 \(\dfrac{a}{c}\),因为只有 \(\dfrac{a}{c}\) 乘上 \(c+dx\) 后常数项才能得到 \(a\),代入待定系数法算一下也可得到一次项是 \(\dfrac{bc-ad}{c^2}\),即 \(\dfrac{a+bx}{c+dx}=\dfrac{a}{c}+\dfrac{bc-ad}{c^2}x\),写个结构体维护一下即可。

这样暴力复杂度是 \(wn^3\) 的,可能有卡常的风险,保险起见我们加上一个小小的优化,就是如果边权为 \(i\) 的倍数的边数 \(<n-1\) 那么直接令 \(f_i=0\),因为不可能存在生成树,加了这个优化以后复杂度显然变成了 \(\dfrac{md(w_i)}{n-1}n^3\),即可通过此题。

然鹅我又搞错模数了所以 WA 了一次

const int MAXM=435;
const int MAXN=30;
const int MAXV=152505;
const int MOD=998244353;
int qpow(int x,int e){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
int n,m,u[MAXM+5],v[MAXM+5],w[MAXM+5];
vector<int> eds[MAXV+5];
int mu[MAXV+5],pr[MAXV/6+5],prcnt=0;
bitset<MAXV+5> vis;
void sieve(int mx){
	mu[1]=1;
	for(int i=2;i<=mx;i++){
		if(!vis[i]){pr[++prcnt]=i;mu[i]=-1;}
		for(int j=1;j<=prcnt&&pr[j]*i<=mx;j++){
			vis[pr[j]*i]=1;
			if(i%pr[j]==0) break;
			else mu[pr[j]*i]=-mu[i];
		}
	}
}
struct line{
	int x,y;
	line(int _x=0,int _y=0):x(_x),y(_y){}
	line operator +(const line &rhs){return line((x+rhs.x)%MOD,(y+rhs.y)%MOD);}
	line operator -(const line &rhs){return line((x-rhs.x+MOD)%MOD,(y-rhs.y+MOD)%MOD);}
	line operator *(const line &rhs){return line(1ll*x*rhs.x%MOD,(1ll*x*rhs.y+1ll*y*rhs.x)%MOD);}
	line operator /(const line &rhs){int iv=qpow(rhs.x,MOD-2);return line(1ll*x*iv%MOD,1ll*iv*iv%MOD*
	((1ll*y*rhs.x%MOD-1ll*x*rhs.y%MOD+MOD)%MOD)%MOD);}
};
line a[MAXN+5][MAXN+5];
int f[MAXV+5],g[MAXV+5];
int main(){
	scanf("%d%d",&n,&m);sieve(MAXV);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u[i],&v[i],&w[i]);
		for(int j=1;j*j<=w[i];j++) if(w[i]%j==0){
			eds[j].pb(i);if(w[i]/j!=j) eds[w[i]/j].pb(i);
		}
	}
	for(int i=1;i<=MAXV;i++){
		if(eds[i].size()<n-1) continue;
		for(int j=1;j<n;j++) for(int k=1;k<n;k++)
			a[j][k]=line(0,0);
		for(int j=0;j<eds[i].size();j++){
			int eid=eds[i][j];//printf("%d\n",eid);
			int U=u[eid]-1,V=v[eid]-1;
			a[U][V]=a[U][V]-line(1,w[eid]);
			a[V][U]=a[V][U]-line(1,w[eid]);
			a[U][U]=a[U][U]+line(1,w[eid]);
			a[V][V]=a[V][V]+line(1,w[eid]);
		} int sgn=1;
		for(int j=1;j<n;j++){
			int t=j;
			for(int k=j+1;k<n;k++) if(a[k][j].x) t=k;
			if(t!=j) sgn=-sgn;
			for(int k=j;k<n;k++) swap(a[t][k],a[j][k]);
			line iv=line(1,0)/a[j][j];
			for(int k=j+1;k<n;k++){
				line mul=line(0,0)-a[k][j]*iv;
				for(int l=j;l<n;l++) a[k][l]=a[k][l]+mul*a[j][l];
			}
		} line ret=line((sgn+MOD)%MOD,0);
		for(int j=1;j<n;j++) ret=ret*a[j][j];
		f[i]=ret.y;
//		printf("%d %d\n",i,f[i]);
	} int ans=0;
	for(int i=1;i<=MAXV;i++){
		for(int j=i;j<=MAXV;j+=i) g[i]=(0ll+g[i]+1ll*f[j]*mu[j/i]+MOD)%MOD;
		ans=(ans+1ll*g[i]*i)%MOD;
	} printf("%d\n",ans);
	return 0;
}
/*
4 5
1 2 6
1 3 8
1 4 9
2 3 12
3 4 18
*/
posted @ 2021-05-02 12:45  tzc_wk  阅读(89)  评论(0编辑  收藏  举报