2021 ICPC上海 H.Life is a Game(Kruskal重构树)

题目描述

题目链接

题目大意

给你一个图,图上有点权和边权。以及q个查询:每个查询给你一个初始位置x和初始能量k。
你每到一个新点上即可获得该点的能量(即点权),但是如果想通过一条边,你的能量总数需要大于边权。
问:可以获取的最大能量数

题目分析

这 道 题 我 们 需 要 用 k r u s k a l 重 构 树 来 解 决 。 这道题我们需要用kruskal重构树来解决。 kruskal

k r u s k a l 重 构 树 : 简 单 来 讲 , 就 是 在 k r u s k a l 算 法 进 行 的 过 程 中 , 我 们 把 最 小 生 成 树 的 边 权 改 为 点 权 。 这 样 , 原 树 kruskal重构树:简单来讲,就是在kruskal算法进行的过程中,我们把最小生成树的边权改为点权。 这样,原树 kruskalkruskal 的 节 点 个 数 变 成 2 n − 1 个 , 并 且 有 着 许 多 有 趣 的 性 质 。 的节点个数变成2n-1个,并且有着许多有趣的性质。 2n1

k r u s k a l 重 构 树 的 性 质 : kruskal重构树的性质: kruskal
1 、 根 据 我 们 构 造 的 过 程 , 这 是 一 个 二 叉 堆 1、根据我们构造的过程,这是一个二叉堆 1
2 、 原 树 两 点 之 间 的 边 权 最 大 值 是 重 构 树 上 两 点 l c a 的 权 值 2、原树两点之间的边权最大值是重构树上两点lca的权值 2lca
3 、 重 构 树 中 代 表 原 树 中 的 点 的 节 点 全 是 叶 子 节 点 , 其 余 节 点 都 代 表 了 一 条 边 的 边 权 。 3、重构树中代表原树中的点的节点全是叶子节点,其余节点都代表了一条边的边权。 3
( k r u s k a l 重 构 图 的 构 造 方 法 在 代 码 里 ) (kruskal重构图的构造方法在代码里) kruskal

对 k r u s k a l 重 构 树 有 了 基 本 的 了 解 之 后 , 我 们 接 着 往 后 看 。 对kruskal重构树有了基本的了解之后,我们接着往后看。 kruskal

首 先 我 们 可 以 先 求 出 所 给 图 的 k r u s k a l 重 构 树 , 然 后 d f s 求 出 树 的 l c a 倍 增 数 组 ( 根 据 重 构 树 的 性 质 我 们 可 以 发 现 , 首先我们可以先求出所给图的kruskal重构树,然后dfs求出树的lca倍增数组(根据重构树的性质我们可以发现, kruskaldfslca k r u s k a l 重 构 树 和 l c a 基 本 上 都 是 一 起 用 的 ) 。 此 外 , 在 d f s 的 过 程 中 , 我 们 让 n w [ u ] = 以 u 为 根 的 子 树 中 所 有 点 的 kruskal重构树和lca基本上都是一起用的)。此外,在dfs的过程中,我们让nw[u]=以u为根的子树中所有点的 kruskallcadfsnw[u]=u 点 权 和 ( 原 图 的 点 ) 点权和(原图的点)

做 完 前 面 的 准 备 之 后 , 我 们 来 正 式 处 理 每 个 查 询 。 做完前面的准备之后,我们来正式处理每个查询。

对 于 一 个 查 询 : u ( 起 点 ) 和 s ( 初 始 值 ) 。 对于一个查询:u(起点)和s(初始值)。 u()s()
我 们 从 起 点 u 开 始 往 上 跳 , 如 果 该 位 置 的 点 权 ( 树 上 的 点 权 为 这 棵 子 树 内 所 有 实 点 的 边 权 最 大 值 ) 小 于 我 们 当 前 我们从起点u开始往上跳,如果该位置的点权(树上的点权为这棵子树内所有实点的边权最大值)小于我们当前 u 能 量 数 ( 实 点 权 和 + 初 始 值 ) , 就 跳 过 去 , 同 时 更 新 能 量 数 ( 新 树 上 的 实 点 权 和 + 初 始 值 ) 。 再 继 续 看 能 不 能量数(实点权和+初始值),就跳过去,同时更新能量数(新树上的实点权和+初始值)。再继续看能不 ++ 能 往 上 跳 … … 一 直 到 不 能 再 往 上 跳 了 为 止 ( 因 为 是 通 过 倍 增 法 向 上 跳 的 , 所 以 单 次 查 找 的 复 杂 度 只 有 l o g 2 n ) 能往上跳……一直到不能再往上跳了为止(因为是通过倍增法向上跳的,所以单次查找的复杂度只有log_2n) log2n

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <algorithm>
#include <iomanip>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=2e5+5,mod=998244353;
struct Edge{				//边集
	int u,v,w;
	bool operator< (const Edge e)const
	{ return w<e.w; }
}e[N];
vector<int> h[N];
LL nw[N],st[N];			//nw[]记录实体图的点权,st[]记录实体图的边权
int p[N],f[N][20];		//f[][]为倍增法lca的fa数组
int find(int x)			//并查集模板
{
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}
int kruskal(int n,int m)		//求kruskal重构树的模板
{
	int cnt=n;
	sort(e,e+m);				//对边排序
	for(int i=0;i<m;i++)		//枚举边
	{
		int u=find(e[i].u),v=find(e[i].v),w=e[i].w;
		if(u!=v)			//如果u-v不连通,则连一条边
		{
			st[++cnt]=w;					//新建一个点,点权为u-v边权w
			p[cnt]=p[u]=p[v]=cnt;			//将根节点置为新点(保证所以实点都为叶节点)
			h[u].push_back(cnt);			//连边
			h[v].push_back(cnt);
			h[cnt].push_back(u);
			h[cnt].push_back(v);
		}
	}
	return cnt;
}
void dfs(int u,int fa)		//求lca的倍增数组
{
	f[u][0]=fa;
	for(int i=1;i<20;i++) f[u][i]=f[f[u][i-1]][i-1];
	for(int v:h[u])
	{
		if(v==fa) continue;
		dfs(v,u);
		nw[u]+=nw[v];		//nw[u]更新为以u为根的子树内所以实点的点权和
	}
}
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int n,m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++) p[i]=i;
	for(int i=1;i<=n;i++) cin>>nw[i];
	for(int i=0;i<m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e[i]={a,b,c};
	}
	n=kruskal(n,m);			//建立重构树,跟新n的大小
	dfs(n,0);				//预处理需要的数组
	st[0]=1e18;				//防止u节点跳过根节点
	while(q--)
	{
		int u,s;
		cin>>u>>s;
		LL ans=nw[u]+s;		//初始化能量数
		while(u!=n)
		{
			int t=u;
			for(int i=19;i>=0;i--)		//只要能往上跳就往上跳(需要能量数>这部分的最大边权)
				if(st[f[u][i]]<=ans) u=f[u][i];
			if(t==u) break;			//如果这一轮没有往上跳,说明不能再往上跳了(退出)
			ans=nw[u]+s;		//更新答案
		}
		cout<<ans<<"\n";
	}
	return 0;
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值