P6624-[省选联考2020A卷]作业题【矩阵树定理,欧拉反演】

1|0正题

题目链接:https://www.luogu.com.cn/problem/P6624


1|1题目大意

n个点的一张图,每条边有权值,一棵生成树的权值是所有边权和乘上边权的gcd,即

val(T)=(i=1n1wei)×gcd(we1,we2,,wen1)

求所有生成树的权值和


1|2解题思路

首先要知道一个东西φI=id,于是我们就有

gcd(w1,w2,,wn)=d|w1,d|w2,,d|wnφ(d)

然后化进这个式子里就是

(i=1n1wi)×d|w1,d|w2,,d|wnφ(d)

然后把φ(d)丢出去就是

d=1nφ(d)×(i=1,d|win1wi)

之后就是怎么快速算后面那个东西的事情了。

一个比较朴素的做法是枚举每一条边产生的贡献,然后固定这条边然后矩阵树求一次答案乘上这条边就好了。

但是这个比较慢,我们可以利用生成函数的思想,每一条边权变为Fi(x)=wix+1的一个多项式,然后所有多项式乘起来之后的一次项系数就是答案了。因为这样只会选择一条边的wi

多项式除法的话用的比较少,这里化一下

ax+bcx+d=zx+wax+b=zcx2+(zd+cw)x+wd

然后二次项我们直接丢掉就有

b=wdw=bd

带进后面那个去就有

a=zd+cbdz=adcbd2

所以

ax+bcx+d=adcbd2x+bd

这样时间复杂度是O( max{wi}(m+n3) )的,好像过不了,可以把边数不够n1的直接不管,这样可以省去很多无用情况


1|3code

#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const ll N=2e5+10,P=998244353; struct fuc{ ll x,y; fuc(ll xx=0,ll yy=0) {x=xx;y=yy;return;} }; ll power(ll x,ll b){ ll ans=1;x%=P; while(b){ if(b&1)ans=ans*x%P; x=x*x%P;b>>=1; } return ans; }; ll inv(ll x){return power(x,P-2);} fuc operator+(fuc a,fuc b) {return fuc((a.x+b.x)%P,(a.y+b.y)%P);} fuc operator-(fuc a,fuc b) {return fuc((a.x-b.x+P)%P,(a.y-b.y+P)%P);} fuc operator*(fuc a,fuc b) {return fuc((a.x*b.y+a.y*b.x)%P,a.y*b.y%P);} fuc operator/(fuc a,fuc b) {return fuc((a.x*b.y-b.x*a.y+P)%P*inv(b.y*b.y)%P,a.y*inv(b.y)%P);} ll n,m,ans,x[N],y[N],w[N],phi[N],pri[N],cnt; bool v[N];fuc a[31][31]; fuc det(){ fuc ans(0,1);ll f=1; for(ll i=1;i<n;i++){ ll w=i; for(ll j=i;j<n;j++) if(a[i][j].y){ if(i!=j)f=-f; w=j;break; } swap(a[i],a[w]); if(!a[i][i].y)return fuc(0,0); ans=ans*a[i][i]; fuc inv=fuc(0,1)/a[i][i]; for(ll j=n-1;j>=i;j--) a[i][j]=a[i][j]*inv; for(ll j=i+1;j<n;j++){ fuc rate=a[j][i]; for(ll k=i;k<n;k++) a[j][k]=a[j][k]-rate*a[i][k]; } } return (ans.x*f+P)%P; } void init(ll n){ phi[1]=1; for(ll i=2;i<=n;i++){ if(!v[i])pri[++cnt]=i,phi[i]=i-1; for(ll j=1;j<=cnt&&i*pri[j]<=n;j++){ v[i*pri[j]]=1; if(i%pri[j]==0){ phi[i*pri[j]]=phi[i]*pri[j]; break; } phi[i*pri[j]]=phi[i]*phi[pri[j]]; } } return; } signed main() { scanf("%lld%lld",&n,&m); ll mx=0; for(ll i=1;i<=m;i++){ scanf("%lld%lld%lld",&x[i],&y[i],&w[i]); x[i]--;y[i]--;mx=max(mx,w[i]); } init(mx); for(ll i=1;i<=mx;i++){ ll cnt=0; for(ll j=1;j<=m;j++) if(w[j]%i==0)cnt++; if(cnt<n-1)continue; memset(a,0,sizeof(a)); for(ll j=1;j<=m;j++) if(w[j]%i==0){ a[x[j]][x[j]]=a[x[j]][x[j]]+fuc(w[j],1); a[y[j]][y[j]]=a[y[j]][y[j]]+fuc(w[j],1); a[x[j]][y[j]]=a[x[j]][y[j]]-fuc(w[j],1); a[y[j]][x[j]]=a[y[j]][x[j]]-fuc(w[j],1); } (ans+=phi[i]*det().x%P)%=P; } printf("%lld\n",ans); return 0; }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/14302396.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(106)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示