CodeForces 1399E1-Weights Division (easy version)(dfs+set)

该篇博客探讨了一种有根树的优化问题,即如何通过最少次数的操作,将树中每条边的权重变为原来的一半,使得所有边权和小于等于给定的目标值S。博主使用深度优先搜索(DFS)遍历树结构,计算每个边影响的叶子节点数量,并利用优先级队列(set)进行动态规划求解,寻找最优操作顺序。博客内容涉及数据结构、图论和算法设计,适合对算法感兴趣的读者阅读。
摘要由CSDN通过智能技术生成

题意:给定你一个根为1号节点的有根树,然后n-1条边每条边都有一个权值w,然后给定你一个操作,每次操作可以把某一条边的边权值变为w/2,向下取整,然后问你最少经过过少次操作可以使得这棵树的所有边权和加起来小于等于S。

思路:假如不考虑一棵树的情况,我们直接可以把每条边的边权值存起来,然后从大到小的选边进行操作等到全部的和小于等于S即可。但对于一棵树来说,选择的走到叶子节点的路径权值和,某个节点向下的边是可以对他所有的子节点的子树路径和产生影响的。
所以我们应该考虑的是让边权值改变是带来影响更大的边我们先更新,所有就可以dfs出每个边连接的节点的子树中,有几条路径能走到叶子节点,也就是有几个叶子节点,用他们我的边权值乘上这个叶子节点个数才是他们能真正做出的影响。

void dfs(int u,int fa = -1)//用u来代表节点 而fa代表他与父节点连接的那条边的序号
{
	if(fa != -1 && G[u].size() == 1){//代表到达了叶子节点
		cnt[fa] = 1;//储存的下标信息也是边的序号的
		return ;
	}
	for(auto i: G[u]){
		int v = i.second;
		if(v == fa) continue;
		dfs(i.first,v);
		if(fa != -1)
			cnt[fa] += cnt[v];
	}
}

再用一个set<pair<int,int>>s,因为set存储pair时是会自动对pair的第一关键字进行排序,所以只需要把二元组cnt[i]*w[i]和i这个编号存入即可。
再另外补充一个prev库函数,会自动取得当前迭代器位置前一个位置的元素,而end()又是指向最后一个元素后面一个元素的位置,所以即可用prev(s.end())来获得集合的最后一个位置。
代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN = 1e5+7;
int n;
//贡献还要看根的子节点的子树中能到达的叶子节点的数目 也就是他能所影响到的路径的数目
ll sum;
//用set完成了一个自动的排序
vector<ll>w,cnt;
vector<vector<pair<ll,ll>>>G;//用一个二维的vector存值 第一位存权值 第二位存边的序号数

ll get_val(ll i)//进行剪枝的时候不能单纯看权值大小,还要看和他相连的叶子节点的个数,也就是他会影响到的路径数
{
	return w[i] * cnt[i] - w[i]/2 * cnt[i];
}

void dfs(ll u,ll fa = -1)//fa 表示它与父节点相连的那条边的编号
{
	if(fa != -1 && G[u].size() == 1){//走到了叶子节点
		cnt[fa] = 1;
		return ;
	}
	for(auto i: G[u]){
		int v = i.second;
		if(v == fa) continue;
		dfs(i.first,v);
		if(fa != -1) cnt[fa] += cnt[i.second];//不是第一条边的情况的话 再加边
	}
}

int main()
{
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%lld",&n,&sum);
		w = cnt = vector<ll>(n);//存下对应编号下的边权值
		G = vector<vector<pair<ll,ll>>>(n+1);//开一个对应大小的vector数组

		for(int i = 1;i < n;i ++){
			ll u,v;
			scanf("%lld%lld%lld",&u,&v,&w[i]);
			G[u].push_back({v,i});
			G[v].push_back({u,i});
		}

		dfs(1);

		set<pair<ll,ll>>s;
		ll cur = 0;
		for(int i = 1;i < n;i ++){
			s.insert({get_val((ll)i),(ll)i});
			cur += w[i]*cnt[i];
		}

		ll ans = 0;
		while(cur > sum){
			int id = s.rbegin()->second;//直接找到集合最后的一个元素
			s.erase(prev(s.end()));//prev返回迭代器指向的上一个元素 s.end()指向最后一个元素之后的指针 所以prev(end)就是集合中最后一个元素
			cur -= get_val(id);
			w[id] /= 2;
			s.insert({get_val(id),id});
			ans++;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值