题意:
就是给你n个物品,然后给你n-1个关系,就是a:b = c:d,也就是a物品和b物品的比值就是c:d。然后每个物品真正的值都是正整数,现在问你这些物品的总和最小为多少。
思考:
当时看到这题感觉肯定和最大公倍数有关。然后想了想咋根据比例处理出最小的总和,想了想是不是并查集呢。看了下样例发现整个图都是联通的,并查集没用了。由于感觉题目比较难,就没仔细看,实际上给你了n-1个关系,很明显在告诉你是一个树。那么既然是个树,各个关系比例还给你了,如果你把第一个点的值就确定为1,然后剩下所有点的值都出来了,当然可能是分数,但是既然要保证每个数是整数,所以第一个点的值至少为多少才能让所有的值都是整数。所以这里就是看所有数的分母的最小公倍数,但是如果直接暴力求出每个数的分母,然后从这些数选每个质因子的出现最大次幂,这样复杂度太高了,是n根号下(分母最大值)。分母最大可以很大所以肯定超时。所以换种想法去维护每个因子出现的最大次幂,其实题目给的比例x,y都是<=2e5的。如果我在dfs中,每走到一个节点,会乘以谁,除以谁。动态的去维护当前分母会出现的各种次幂的最大值是多少,记得搜完要还原现场,因为这个分母的次幂,不是看整个图的,而是看1到某个点这条路径上的,这一点要想好。最后就把每个质因子的次幂都求出来了,自然最小公倍数就求出来了。再写一个dfs去求每个数的值就行了,每个数的值就是父亲父亲到儿子的比例即可。
这题还有一个就是维护出每个数他的所有质因子会出现多少,放在数组容器里。
因为每个数都是1号点的多少多少倍,所以1号点越小,总体肯定也是越小的。
代码:
#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int mod = 998244353,inf = 1e18;
const int N = 2e5+10,M = 2e5;
int T,n,m,k;
int va[N];
int sum[N];
vector<int > v[N];
map<int,int > cnt,maxn;
vector<pair<int,pair<int,int>> > e[N];
void init(int r)
{
for(int i=2;i<=r;i++)
{
if(!v[i].size())
{
for(int j=i;j<=r;j+=i)
{
int x = j;
while(x%i==0) x/=i,v[j].pb(i);//每个数他的所有质因子都放进去,出现多少次放进去多少次。
}
}
}
}
int ksm(int a,int b)
{
int sum = 1;
while(b)
{
if(b&1) sum = sum*a%mod;
a = a*a%mod;
b >>= 1;
}
return sum;
}
void get(int now,int p)
{
for(auto t:e[now])
{
int spot = t.fi,a = t.se.fi,b = t.se.se;
if(spot==p) continue;
for(auto t:v[b]) cnt[t]--; //spot从now转移是除以a,乘以b,因为要看分母,所以a的因子都++,b的因子都--,尽管可能减去b的时候出现负数,但是我们只看最大值,所以只有在加加的时候更新最大值。
for(auto t:v[a]) cnt[t]++,maxn[t] = max(maxn[t],cnt[t]);
get(spot,now);
for(auto t:v[b]) cnt[t]++; //由于不是看整个图,而是看从1到某个点的路径上的,所以要还原一下
for(auto t:v[a]) cnt[t]--;
}
}
void dfs(int now,int p)
{
for(auto t:e[now])
{
int spot = t.fi,a = t.se.fi,b = t.se.se;
if(spot==p) continue;
sum[spot] = sum[now]*b%mod*ksm(a,mod-2)%mod; //求出每个点的值就好了。
dfs(spot,now);
}
}
signed main()
{
IOS;
init(2e5);
cin>>T;
while(T--)
{
cin>>n;
cnt.clear();maxn.clear();
for(int i=1;i<=n;i++)
{
sum[i] = 0;
e[i].clear();
}
for(int i=1;i<n;i++)
{
int a,b,c,d;
cin>>a>>b>>c>>d;
e[a].pb({b,{c,d}});
e[b].pb({a,{d,c}});
}
get(1,0);
int ans = 1;
for(auto t:maxn) ans = ans*ksm(t.fi,t.se)%mod; //求出最小公倍数,也就是一号点的值。
sum[1] = ans;dfs(1,0);
int anw = 0;
for(int i=1;i<=n;i++) anw = (anw+sum[i])%mod; //所有点的值都加起来
anw = (anw%mod+mod)%mod;
cout<<anw<<"\n";
}
return 0;
}
总结:
多多思考,不要惧怕,换种思维方式,多多积累操作。