CFdiv1+2-Potion Brewing Class-(树上乘除+因子)

D

题意:
就是给你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;
}

总结:
多多思考,不要惧怕,换种思维方式,多多积累操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值