Codeforces Round 888 (Div. 3)G. Vlad and the Mountains(Kruskal重构树,离线并查集)

原题链接:G. Vlad and the Mountains
题目大意
给出n 个点 m 条边的无向图,每个点有点权hi 。从点i走到点j 会消耗hj−hi的能量,如果小于0 就是恢复对应绝对值的能量。给出q 个询问,每个询问包含起点s,终点t,能量e,能量值在移动的过程中不能低于0,问能否从s走到t。
数据范围:1≤t≤104,2≤n≤2⋅105 , 1≤m≤min(n⋅(n−1)/2,2⋅105),1≤hi≤109,1≤u,v≤n , u≠v,1≤q≤2⋅105,1≤a,b≤n , 0≤e≤109,保证所有测试案例的n总和不超过2⋅105.同样的保证也适用于m和q。
解题思路
首先分析题目,对于是否能从点s走到点t,只需要考虑点s到点t的路径中高度最高的山的高度即可,即hmax<=hs+e
如何找到两点之间的路径中最小的点呢?
如果采用暴力做法,对每次询问都进行一次暴力查询,最坏时间复杂度为O(m*q),会超时,可以考虑Kruskal重构树的做法
我们只考虑路径中高度最高的山的高度,因此可以把每一条边的边u,v权设为max(hu,hv),将每次询问的可通过的最高点设为hs+e,然后将每条边的边权和每次询问的最高高度进行排序`

 sort(ed+1,ed+1+m,[&](edge p1,edge p2){
		return p1.w<p2.w;
	});
	sort(p+1,p+1+q,[&](que p1,que p2){
		return p1.w<p2.w;
	});`

然后是Kruskal重构树操作,对于每个询问,将小于当前询问最高高度的边加入最小生成树中,全部加入后,判断当前询问的两个点是否连通,将结果存入答案数组中

for(int i=1;i<=q;i++)
	{
		//将小于当前询问最高高度的边加入最小生成树中
		while(j<=m&&ed[j].w<=p[i].w) {
			if(find(p[i].a)!=find(p[i].b))merge(ed[j].u, ed[j].v);
			j++;
		}
		ans[p[i].id]=(find(p[i].a)==find(p[i].b));//如果联通,则能走到
	}

然后输出答案即可

AC代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
int h[200005],fa[200005],ans[200005];

struct edge
{
	int u,v,w;
}ed[200005];

struct que
{
	int w,a,b,id;
}p[200005];

int find(int x) 
{
     return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}

void merge(int x,int y)
{
    fa[find(x)]=find(y);
}

void solve() {
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>h[i];
		fa[i]=i;
	}
	
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		ed[i].w=max(h[u],h[v]);
		ed[i].u=u;ed[i].v=v;
	}
	
	int q;
	cin>>q;
	for(int i=1;i<=q;i++)
	{
		ans[i]=0;
		int s,t,e;
		cin>>s>>t>>e;
		p[i].w=h[s]+e;
		p[i].a=s;
		p[i].b=t;
		p[i].id=i;//当前询问编号
	}
	
	sort(ed+1,ed+1+m,[&](edge p1,edge p2){
		return p1.w<p2.w;
	});
	sort(p+1,p+1+q,[&](que p1,que p2){
		return p1.w<p2.w;
	});
	
	int j=1;//记录当前操作过的边数
	for(int i=1;i<=q;i++)
	{
		//将小于当前询问最高高度的边加入最小生成树中
		while(j<=m&&ed[j].w<=p[i].w) {
			if(find(ed[j].u)!=find(ed[j].v))merge(ed[j].u, ed[j].v);
			j++;
		}
		ans[p[i].id]=(find(p[i].a)==find(p[i].b));//如果联通,则能走到
	}
	
	for(int i=1;i<=q;i++)
	{
		if(ans[i])cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	cout<<endl;
}
signed main() {
	int t=1;
	cin >> t;
	while (t--)solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值