(2021上海站icpc)Life is a game(Kruscal重构树)

17 篇文章 0 订阅

题目链接:登录—专业IT笔试面试备考平台_牛客网

题意:给你一个图,图上有点权和边权,然后给你一个初始位置和初始能量,你每到达一个点都会获得该点的能量(也就是点权),但是你经过某条边的条件是你当前所拥有的能量大于等于该边权(你经过这条边后你的能量并不会减少,但是你的能量要大于等于该边权才能经过该边,每条边可以重复经过),给你多个询问,每个询问给你一个初始位置和初始能量,然后问你最多能获得的能量。

分析:先来贪心地想一下这个问题,就是把我们当前可以到达的点看成一个连通块,那么我们现在拥有的能量就是整个连通块所具有的点权和,然后我们找一个与当前可到达连通块相连且边权最小的边去走(无论其他边能不能走,我们都优先走当前能够走的具有最小边权的边),直到走到我们不能走位置。

这不得不使我们想起kruscal重构后(边权按照从小到大排序)的树所具有的优良性质:大根堆,那么我们只要可以到达每个节点,那么以他为根的子树中所有节点我们都能够到达,而且这个节点是与连通块外部连接的必要通路,且走到这个节点所需要的能量也是走到外界所需要的最小能量,这刚好符合我们的贪心思想,所以本岛题目就需要用kruscal重构树来解决了。

如果不懂kruscal重构树的小伙伴可以看这里:Kruskal重构树_AC__dream的博客-CSDN博客

就是我们每次判断当前节点的父节点能否到达,判断条件需要注意一下,并不是判断初始能量加上以当前节点为根的子树中的叶子节点权值和与当前节点点权的关系,因为以当前节点为根的叶子节点的能量和是到达该点之后才能获得的,所以判断条件应该是判断初始能量加上以当前节点子节点为根的子树中的叶子节点权值和与当前节点点权的关系,如果大于等于当前节点就可以到达,反之不能到达。如果能够到达,我们就令当前能量为以当前点为根的子树中所有叶子节点的点权和+初始能量,因为kruscal除了原始点之外的其他点都是虚拟的,都是原来图中的边权,所以我们需要处理出来以每个节点为根的子树中的叶子节点的点权和,但是如果只是单独这样进行判断是会超时的,因为有些毒瘤数据会让kruscal重构后的树呈现类链式形状,这样就会使得一个一个判断的方法超时,所以我们可以用倍增的思想

如果不懂树上倍增的小伙伴可以看这里:最近公共祖先(LCA)(树上倍增)_AC__dream的博客-CSDN博客

由于我们每次判断能不能到达一个点都是判断初始能量加上以当前节点子节点为根的子树中的叶子节点权值和与当前节点边权的关系,所以我们就可以记录一个mx数组,mx[i][j]表示通过i到达i以上2^j个父亲节点的这段区间的最少需要的初始能量,比如通过j到达他直接父节点x的最少初始能量就是x的点权-以j为根的子树中的所有叶子节点的权值和,这也就是mx[j][0],更新方式类似于倍增更新方式,剩下的就是一个for循环判断初始点最大能够到达的点的位置了,答案就是以所能够到达的最大点为根的子树中叶子节点的权值和与初始能量的和。

下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N=4e5+10;
int h[N],e[N],ne[N],w[N],idx,fu[N];
int fa[N][23];
ll mx[N][23];//mx[i][j]表示i到i的第2^j个父亲节点之间点权的最大值
ll f[N];//f[i]表示以i为根的子树中叶子节点的权值和
void add(int x,int y)
{
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
struct node{
	int u,v,z;
}p[N];
bool cmp(node a,node b)
{
	return a.z<b.z;
}
int find(int x)
{
	if(fu[x]!=x) fu[x]=find(fu[x]);
	return fu[x];
}
ll dfs(int x,int father)
{
	fa[x][0]=father;
	for(int i=1;i<=20;i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		f[x]+=dfs(j,x);
	}
	return f[x];
}
void dfs2(int x,int father)
{
	mx[x][0]=w[father]-f[x];
	for(int i=1;i<=20;i++)
		mx[x][i]=max(mx[x][i-1],mx[fa[x][i-1]][i-1]);
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		dfs2(j,x);
	}
}
int main()
{
	int n,m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&w[i]);
		f[i]=w[i];
	} 
	for(int i=1;i<=2*n-1;i++)
		h[i]=-1,fu[i]=i;
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].z);
	sort(p+1,p+m+1,cmp);
	for(int i=1,t=1;t<n;i++)
	{
		int f1=find(p[i].u),f2=find(p[i].v);
		if(f1==f2) continue;
		fu[f1]=fu[f2]=t+n;
		w[t+n]=p[i].z;
		add(t+n,f1);add(t+n,f2);
		t++;
	}
	dfs(2*n-1,0);
	dfs2(2*n-1,0);
	while(q--)
	{
		int begin,t;
		scanf("%d%d",&begin,&t);
		for(int i=20;i>=0;i--)
			if(mx[begin][i]<=t&&fa[begin][i]) 
				begin=fa[begin][i];
		printf("%lld\n",f[begin]+t);
	}
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值