原题链接: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;
}