2020全國統一省選day2 作業題

題目


正解

大套路題。
看到之後是個人都知道要先反演一下推推式子:
(爲了方便表示,題目中的weiw_{e_i}直接用eie_i表示了)
T(ei)(gcdei)=T(ei)d(gcdei)ϕ(d)=dϕ(d)T,deiei\sum_T (\sum e_i)(\gcd e_i) \\ =\sum_T (\sum e_i)\sum_{d|(\gcd e_i)} \phi(d) \\ =\sum_d \phi(d) \sum_{T,d|e_i}\sum e_i
我們要求的是後面的那個東西。用人話說就是:所有邊權爲dd的倍數的邊,組成的生成樹的邊權和之和。
然後很容易想到矩陣樹。但是由於每個生成樹的貢獻是ei\sum e_i而不是ei\prod e_i,所以要用到一個大套路:
邊權爲eie_i的邊,丟到基爾霍夫矩陣中的邊權爲1+eix1+e_ix。矩陣樹定理搞完之後求一次項係數即是答案。

可以這麼理解:對於某條邊,如果計算它的貢獻,就是要計算它的邊權以及生成樹包含這條邊的方案數。

計算的時候保留常數項和一次項即可。至於求逆,可以手算出a+bxa+bx的逆元爲1aba2x\frac{1}{a}-\frac{b}{a^2}x

大優化:計算之前先判斷一下符合條件的邊是否能組成至少一個生成樹。
一個邊權的因數個數最多爲144144。於是時間大概就是O(144mn3n1)=O(144n4)O(\frac{144mn^3}{n-1})=O(144n^4)
顯然實際上也不可能真正達到這個時間複雜度。所以是可以過的。


代碼

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 35
#define M N*N
#define MX 152505
#define mo 998244353
#define ll long long
ll qpow(ll x,ll y=mo-2){
	ll r=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			r=r*x%mo;
	return r;
}

int n,m;
struct edge{
	int u,v,w;
} ed[M];

int mx,p[MX],np;
bool inp[MX];
int phi[MX];
void getphi(int mx){
	inp[1]=1,phi[1]=1;
	for (int i=2;i<=mx;++i){
		if (!inp[i]){
			p[++np]=i;
			phi[i]=i-1;
		}
		for (int j=1;j<=np && i*p[j]<=mx;++j){
			inp[i*p[j]]=1;
			if (i%p[j]==0){
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
			phi[i*p[j]]=phi[i]*(p[j]-1);
		}
	}
}

struct Po{
	int x0,x1; 
	bool eql0(){return x0==0 && x1==0;}
	Po inv(){
		int m=qpow(x0);
		return (Po){m%mo,(ll)(mo-x1)*m%mo*m%mo};
	}
};
Po operator-(Po a){return (Po){(mo-a.x0)%mo,(mo-a.x1)%mo};}
Po operator+(Po a,Po b){return (Po){(a.x0+b.x0)%mo,(a.x1+b.x1)%mo};}
Po operator-(Po a,Po b){return (Po){(a.x0-b.x0+mo)%mo,(a.x1-b.x1+mo)%mo};}
Po operator*(Po a,Po b){return (Po){(ll)a.x0*b.x0%mo,((ll)a.x0*b.x1+(ll)a.x1*b.x0)%mo};}
Po &operator+=(Po &a,Po b){return a=a+b;}
Po &operator-=(Po &a,Po b){return a=a-b;}
Po &operator*=(Po &a,Po b){return a=a*b;}
int determinant(Po m[N][N]){
	int o=1;
	for (int i=1;i<n;++i){
		int j=i;
		for (;j<n && m[j][i].eql0();++j);
		if (i!=j){
			o=mo-o;
			for (int k=i;k<n;++k)
				swap(m[i][k],m[j][k]);
		}
		Po inv=-m[i][i].inv();
		for (++j;j<n;++j)
			if (!m[j][i].eql0()){
				Po tmp=inv*m[j][i];
				for (int k=i;k<n;++k)
					m[j][k]+=m[i][k]*tmp;
			}
	}
	Po r={o,0};
	for (int i=1;i<n;++i)
		r*=m[i][i];
	return r.x1;
}

int dsu[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
Po mat[N][N];
int calc(int d){
	for (int i=1;i<=n;++i)
		dsu[i]=i;
	for (int i=1;i<=m;++i)
		if (ed[i].w%d==0){
			int x=getdsu(ed[i].u),y=getdsu(ed[i].v);
			if (x!=y)
				dsu[x]=y;
		}
	getdsu(1);
	for (int i=2;i<=n;++i)
		if (getdsu(i)!=dsu[1])
			return 0;
	memset(mat,0,sizeof mat);
	for (int i=1;i<=m;++i)
		if (ed[i].w%d==0){
			int u=ed[i].u,v=ed[i].v;
			Po w={1,ed[i].w};
			mat[u][u]+=w,mat[v][v]+=w;
			mat[u][v]-=w,mat[v][u]-=w;
		}
	return determinant(mat);
}
int main(){
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout); 
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;++i){
		scanf("%d%d%d",&ed[i].u,&ed[i].v,&ed[i].w);
		mx=max(mx,ed[i].w);
	}
	getphi(mx);
	ll ans=0;
	for (int i=1;i<=mx;++i)	
		ans+=(ll)phi[i]*calc(i)%mo;
	ans%=mo;
	printf("%lld\n",ans);
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章