loj3304.「联合省选 2020 A」作业题
怎么会有二合一题目放在压轴题
前半部分直接欧拉反演,没啥营养:
\(\sum\limits_T(\sum\limits_{i=1}^{n-1}w_{e_i})\gcd(w_{e_1},\cdots,w_{e_{n-1}})\)
\(=\sum\limits_T(\sum\limits_{i=1}^{n-1}w_{e_i})\sum\limits_{d\mid w_{e_1}\cdots d\mid w_{e_{n-1}}}\varphi(d)\)
\(=\sum\limits_{d=1}^{maxn}\varphi(d)\sum\limits_{T,d\mid w_{e_1},\cdots,d\mid w_{e_{n-1}}}\sum\limits_{i=1}^{n-1}w_{e_i}\)
然后显然是要 \(Matrix-Tree\) 定理,但是普通的矩阵树都是求 \(\prod\limits_Tval_i\),而不能求 \(\sum\limits_Tval_i\)。
有一个经典 trick:我们把每条边变成一次多项式 \(w_i+1\) ,然后按照正常的方式求行列式,得到的一次项系数就是答案。这是因为每个乘出一次的部分都相当于钦定了选一条边,其他边任意选的方案。
然后就没了。
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
const int mod=998244353;
inline int pw(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
struct edge
{
int x,y,w;
}e[1001];
struct cp
{
int x,y;
cp(int x_=0,int y_=0):
x(x_),y(y_){}
cp operator +(const cp &other) const
{
return cp((x+other.x)%mod,(y+other.y)%mod);
}
cp operator +=(const cp &other)
{
x=(x+other.x)%mod;
y=(y+other.y)%mod;
return *this;
}
cp operator -(const cp &other) const
{
return cp((x-other.x+mod)%mod,(y-other.y+mod)%mod);
}
cp operator -=(const cp &other)
{
x=(x-other.x+mod)%mod;
y=(y-other.y+mod)%mod;
return *this;
}
cp operator *(const cp &other) const
{
return cp((x*other.y%mod+y*other.x%mod)%mod,y*other.y%mod);
}
cp operator *=(const cp &other)
{
x=(x*other.y%mod+y*other.x%mod)%mod;
y=y*other.y%mod;
return *this;
}
cp operator /(const cp &other) const
{
return cp((x*other.y%mod-y*other.x%mod+mod)%mod*pw(other.y*other.y%mod,mod-2)%mod,y*pw(other.y,mod-2)%mod);
}
}g[31][31];
int maxn,ans,n,m,phi[200001],cnt,p[200001],sum[200001];
bool prime[200001];
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x;
}
inline void init()
{
phi[1]=1;
for(register int i=2;i<=maxn;++i)
{
if(!prime[i])
{
p[++cnt]=i;
phi[i]=i-1;
}
for(register int j=1;j<=cnt&&i*p[j]<=maxn;++j)
{
prime[i*p[j]]=1;
if(i%p[j]==0)
{
phi[i*p[j]]=p[j]*phi[i];
break;
}
else
phi[i*p[j]]=(p[j]-1)*phi[i];
}
}
}
inline int solve()
{
cp ans=cp(0,1);
bool tag=0;
for(register int i=2;i<=n;++i)
{
for(register int j=i+1;j<=n;++j)
if(!g[i][i].y&&g[j][i].y)
{
swap(g[i],g[j]);
tag^=1;
break;
}
ans*=g[i][i];
cp d=cp(0,1)/g[i][i];
for(register int j=i;j<=n;++j)
g[i][j]*=d;
for(register int j=i+1;j<=n;++j)
{
d=g[j][i];
for(register int k=i;k<=n;++k)
g[j][k]-=g[i][k]*d;
}
}
return tag? (mod-ans.x)%mod:ans.x;
}
signed main()
{
n=read(),m=read();
for(register int i=1;i<=m;++i)
{
e[i].x=read(),e[i].y=read(),maxn=max(e[i].w=read(),maxn);
for(register int j=1;j*j<=e[i].w;++j)
if(e[i].w%j==0)
{
++sum[j];
if(j*j!=e[i].w)
++sum[e[i].w/j];
}
}
init();
for(register int d=1;d<=maxn;++d)
{
if(sum[d]<n-1)
continue;
for(register int i=1;i<=n;++i)
for(register int j=1;j<=n;++j)
g[i][j]=cp(0,0);
for(register int i=1;i<=m;++i)
{
if(e[i].w%d)
continue;
g[e[i].x][e[i].x]+=cp(e[i].w,1);
g[e[i].y][e[i].y]+=cp(e[i].w,1);
g[e[i].x][e[i].y]-=cp(e[i].w,1);
g[e[i].y][e[i].x]-=cp(e[i].w,1);
}
ans=(ans+phi[d]*solve()%mod)%mod;
}
printf("%lld\n",ans);
return 0;
}