Potion Brewing Class(CF 1654 D)
题目大意
有\(t\)组询问,每组询问给你一棵有\(n\)个节点,\(n-1\)条边的树,树上每两个相连点之间的权值成比例,每组询问问所有权值都是整数且最小时的权值和,答案对\(998244353\)取模。\((1\leq t\leq10^4,2\leq n\leq2\cdot10^5,\sum n\leq2\cdot10^5)\)
思路
首先很容易想到\(dfs\)一下然后从叶子节点传答案上来,但是发现不是能写,于是就换一个思路。这次我们设根节点的权值是\(1\),然后往下\(dfs\),于是就可以得到\(n\)个最简分数,然后约个分就结束了。但是再仔细想一想,真的嘛?很容易又能想到分母还是会很大然后爆\(long\ long\),然后就会寄。所以我们又能想到一次一次更新,每到达一个新的点,我们可以分别对分子分母进行质因子分解,然后记一个\(a[i]\)和\(b[i]\)分别表示质因子\(i\)的个数和质因子\(i\)出现过的最多次数,对于分母的质因子,我们在原基础上加上他们的个数,对于分子就减去,然后最后就可以先把每个点对于模数取模后的值加起来,然后再乘上分母的\(LCM\),然后这道题就能轻松\(AC\)啦!
代码
#include<bits/stdc++.h>
using namespace std;
long long mod=998244353;
const int maxn=200005;
struct EDGE
{
int next,to,x,y;
}edge[maxn<<1];
int head[maxn];
int cnt;
void add(int u,int v,int x,int y)
{
edge[cnt].x=x;
edge[cnt].y=y;
edge[cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
long long a[200005],b[200005];
long long poww(long long a,long long n)
{
long long ans=1;
while(n)
{
if(n&1)ans=ans*a%mod;
a=a*a%mod;
n>>=1;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
int _;
scanf("%d",&_);
while(_--)
{
int n;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v,x,y;
scanf("%d%d%d%d",&u,&v,&x,&y);
add(u,v,x,y);
add(v,u,y,x);
}
int maxx=0;
long long tot=0;
function<void(int,int)>update=[&](int x,int w)
{
for(int i=2;i*i<=x;i++)
{
while(x%i==0)
{
a[i]+=w;
x/=i;
}
if(a[i]>b[i])
{
b[i]=a[i];
maxx=max(maxx,i);
}
}
if(x!=1)
{
a[x]+=w;
if(a[x]>b[x])
{
b[x]=a[x];
maxx=max(maxx,x);
}
}
};
function<void(int,int,long long)>dfs=[&](int s,int fa,long long val)
{
tot=(tot+val)%mod;
for(int i=head[s];~i;i=edge[i].next)
{
int it=edge[i].to,x=edge[i].x,y=edge[i].y;
if(it==fa)continue;
update(y,-1);
update(x,1);
dfs(it,s,val*y%mod*poww(x,mod-2)%mod);
update(x,-1);
update(y,1);
}
};
dfs(1,0,1);
for(int i=2;i<=maxx;i++)
{
if(b[i])
{
tot=tot*poww(i,b[i])%mod;
b[i]=0;
}
}
printf("%lld\n",tot);
if(_)
{
for(int i=1;i<=cnt;i++)
edge[i]={0,0,0,0};
for(int i=1;i<=n;i++)
head[i]=-1;
cnt=0;
}
}
return 0;
}

浙公网安备 33010602011771号