题解 P6624 【[省选联考 2020 A 卷] 作业题】
更好的阅读体验
题目链接
前置知识:(1) 矩阵树定理。(2) 多项式四则运算(加、减、乘、求逆(牛顿迭代))。
网上的介绍很多。这里就不细讲了。
首先要把这个
众所周知,
我们把
然后就可以把这个
其中,
普通的矩阵树定理,可以用来求所有生成树的边权积之和。其中最为大家所熟知的应用,就是当所有边权都为
但是本题中,要求的是边权和,而不是“边权积之和”。一种朴素的想法是考虑每一条边的贡献,也就是求【包含这条边的生成树数量】。它又等于【原图的生成树数量】减去【原图去掉这条边后的生成树数量】。对每条边都求一次【原图去掉这条边后的生成树数量】,时间复杂度
求所有生成树的边权和,其实有更好的方法。我们把每条边的边权,看做一个多项式:
如此以来,求所有生成树的边权和的问题,当我们把边权换成这样一个多项式后,就转化为了求所有生成树的边权积之和的问题。只不过,现在新的边权,不再是一个数,而是一个多项式。所以我们只需要定义出多项式的四则运算,就可以直接用矩阵树定理求解了。另外,我们只关心多项式的“一次项”系数,所以更高的项可以舍去。换句话说,所有的多项式运算,可以在
多项式的加、减、乘运算都比较简单。不了解的可以看如下代码:
typedef pair<int,int> pii;
#define fi first
#define se second
//为了便于理解,这里就不取模了
pii operator+(const pii& a,const pii& b){
return mk(a.fi+b.fi,a.se+b.se);
}
pii operator-(const pii& a,const pii& b){
return mk(a.fi-b.fi,a.se-b.se);
}
pii operator*(const pii& a,const pii& b){
return mk(a.fi*b.fi,a.se*b.fi+b.se*a.fi);
}
多项式的除法,因为在
对本题来说,我们就不需要递归了。先求出常数项,也就是求逆元。然后把常数项作为
pii inv(const pii& a){
int t=pow_mod(a.fi,MOD-2);
return mk(2*t,0)-mk(t,0)*mk(t,0)*a;
}
pii operator/(const pii& a,const pii& b){
return a*inv(b);
}
还有一个小细节是,高斯消元求行列式的时候,如果当前行的(要作为被除数)的这个多项式,常数项为
至此,所有四则运算都可以
发现对于一个
参考代码(在LOJ查看):
//problem:LOJ3304
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
pii operator+(const pii& lhs,const pii& rhs){
return mk(mod1(lhs.fi+rhs.fi),mod1(lhs.se+rhs.se));
}
pii operator-(const pii& lhs,const pii& rhs){
return mk(mod2(lhs.fi-rhs.fi),mod2(lhs.se-rhs.se));
}
pii operator*(const pii& lhs,const pii& rhs){
return mk((ll)lhs.fi*rhs.fi%MOD,((ll)lhs.se*rhs.fi+(ll)rhs.se*lhs.fi)%MOD);
}
pii inv(const pii& a){
//assert(a.fi!=0);
int t=pow_mod(a.fi,MOD-2);
return mk(2LL*t%MOD,0)-mk(t,0)*mk(t,0)*a;//牛顿迭代
}
pii operator/(const pii& lhs,const pii& rhs){
return lhs*inv(rhs);
}
pii operator-(const pii& rhs){
return mk(mod2(-rhs.fi),mod2(-rhs.se));
}//负号
pii& operator+=(pii& lhs,const pii& rhs){
lhs=lhs+rhs;
return lhs;
}
pii& operator-=(pii& lhs,const pii& rhs){
lhs=lhs-rhs;
return lhs;
}
pii& operator*=(pii& lhs,const pii& rhs){
lhs=lhs*rhs;
return lhs;
}
pii& operator/=(pii& lhs,const pii& rhs){
lhs=lhs/rhs;
return lhs;
}
const int MAXW=152501;
int p[MAXW+5],cnt,phi[MAXW+5];
bool v[MAXW+5];
void sieve(int lim){
phi[1]=1;
for(int i=2;i<=lim;++i){
if(!v[i]){
phi[i]=i-1;
p[++cnt]=i;
}
for(int j=1;j<=cnt && p[j]*i<=lim;++j){
v[i*p[j]]=1;
if(i%p[j]==0){
phi[i*p[j]]=phi[i]*p[j];
break;
}
phi[i*p[j]]=phi[i]*phi[p[j]];
}
}
}//线性筛,求phi
const int MAXN=30;
const int MAXM=MAXN*(MAXN-1)/2;
int n,m;
struct Edge{
int u,v,w;
}e[MAXM+5];
struct Matrix{
int n;
pii a[MAXN+5][MAXN+5];
Matrix(){
n=0;
memset(a,0,sizeof(a));
}
};
pii get_det2(Matrix A,int i){
//常数项全部为0
pii res=mk(1,0);
int line=0;
for(int j=i;j<=A.n;++j){
if(A.a[j][i].se!=0){
line=j;
break;
}
}
if(!line)return mk(0,0);
if(line!=i){
for(int j=1;j<=A.n;++j)swap(A.a[i][j],A.a[line][j]);
res=-res;
}
int _inv=pow_mod(A.a[i][i].se,MOD-2);
for(int j=i+1;j<=A.n;++j){
int t=(ll)A.a[j][i].se*_inv%MOD;
for(int k=1;k<=A.n;++k)
sub(A.a[j][k].se,(ll)A.a[i][k].se*t%MOD);
}
res*=A.a[i][i];
return res;
}
pii get_det(Matrix A){
pii res=mk(1,0);
for(int i=1;i<=A.n;++i){
int line=0;
for(int j=i;j<=A.n;++j){
if(A.a[j][i].fi!=0){
line=j;
break;
}
}
if(line==0){
res*=get_det2(A,i);
continue;
}
if(line!=i){
for(int j=1;j<=A.n;++j)swap(A.a[i][j],A.a[line][j]);
res=-res;
}
pii _inv=inv(A.a[i][i]);
for(int j=i+1;j<=A.n;++j){
pii t=A.a[j][i]*_inv;
for(int k=1;k<=A.n;++k)
A.a[j][k]-=A.a[i][k]*t;
}
res*=A.a[i][i];
}
return res;
}
int main() {
cin>>n>>m;
int max_w=0;
for(int i=1;i<=m;++i){
cin>>e[i].u>>e[i].v>>e[i].w;
max_w=max(max_w,e[i].w);
}
sieve(max_w);
int ans=0;
for(int i=1;i<=max_w;++i){
int cnt_e=0;
for(int j=1;j<=m;++j)if(e[j].w%i==0)cnt_e++;
if(cnt_e<n-1)continue;
Matrix mat;
for(int j=1;j<=m;++j)if(e[j].w%i==0){
mat.a[e[j].u][e[j].u]+=mk(1,e[j].w);
mat.a[e[j].v][e[j].v]+=mk(1,e[j].w);
mat.a[e[j].u][e[j].v]-=mk(1,e[j].w);
mat.a[e[j].v][e[j].u]-=mk(1,e[j].w);
}
mat.n=n-1;
pii det=get_det(mat);
ans=((ll)ans+(ll)phi[i]*det.se)%MOD;
}
cout<<ans<<endl;
return 0;
}