诸位大佬把思路讲的很清晰了,我主要补充一下实现。
思路
考虑:如果一个询问的答案是肯定的,它对路径上所有点的要求。
询问为 a b e
。
因为只有 \(e\) 点能量,所以能走到的最大高度只有 \(h_a + e\),没有最小高度。若路径上所有点的点权都在这个范围内,这个询问成立。
问题转化成:\(a\) 到 \(b\) 是否存在一条路径,使得其上的所有点权都小于等于 \(h_a + e\)。
那么就很简单了,把询问离线下来,按照 \(h_a + e\) 从小到大排序,再依次处理每个询问。
每次询问,在新图中加上刚刚满足点权 \(>h_a + e\) 的点,若一条边的两端点都被加上了,才连边。
时间复杂度
套了三层循环,但是第二层总共只会执行 \(n\) 次,第三层总共只会执行 \(2m\) 次左右。
复杂度 \(O(n\cdot\log n+q\cdot\log q+m\cdot\operatorname{\alpha}(n))\)。
//Problem: CF1851G
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define i32 INT_MAX
#define i64 LONG_LONG_MAX
#define pii pair<int, int>
#define pll pair<long long, long long>
#define push_back pb
typedef long long ll;
using namespace std;
const int N = 2e5 + 10;
ll read(){ll x=0,f=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}return x*f;}
void print(ll x){if(x<0)putchar('-'),x=-x;if(x>9)print(x/10);putchar(x%10+48);}
int t, n, m, que, cnte;
int a[N], b[N], head[N], rt[N], ans[N], vst[N];
struct edge {
int v, nxt;
} e[N << 1];
struct query {
int u, v, e, id; //离线询问
} q[N];
void adde(int u, int v) {
e[++cnte] = (edge){v, head[u]};
head[u] = cnte;
}
bool cmp(query x, query y) {
return a[x.u] + x.e < a[y.u] + y.e; //按 h_a + e 排序询问
}
bool cmp1(int x, int y) {
return a[x] < a[y];
}
int find(int x) {
if(rt[x] == x) return x;
return rt[x] = find(rt[x]);
}
void merge(int u, int v) { //并查集
// cout << "merge: " << u << ' ' << v << '\n';
if(find(u) != find(v)) rt[find(u)] = find(v);
}
int main() {
// std::ios::sync_with_stdio(false);
cin >> t;
while(t--) {
memset(vst, 0, sizeof vst);
cnte = 0;
memset(head, 0, sizeof head); //多测不清空,___
n = read(), m = read();
for(int i = 1; i <= n; i++) {
a[i] = read();
b[i] = i; // b 数组:实现对点权的排序
rt[i] = i; //并查集
}
for(int i = 1; i <= m; i++) {
int u = read(), v = read();
adde(u, v);
adde(v, u);
}
que = read();
for(int i = 1; i <= que; i++)
q[i].u = read(), q[i].v = read(), q[i].e = read(), q[i].id = i;
sort(q + 1, q + que + 1, cmp); //排序离线询问
sort(b + 1, b + n + 1, cmp1); //点权排序,b数组记录按点权排序后的顺序。
int j = 1;
for(int i = 1; i <= que; i++) { //循环询问
for(; j <= n; j++) { //这里是按点权大小顺序遍历每个点
if(a[b[j]] > a[q[i].u] + q[i].e)//如果满足: 点权 > h_a + e
break;
vst[b[j]] = 1; // 标记已被加入
for(int k = head[b[j]]; k; k = e[k].nxt) {
if(vst[e[k].v]) merge(b[j], e[k].v);//判断另一端点若也被加入了,则连边。
}
}
ans[q[i].id] = (find(q[i].u) == find(q[i].v));
}
for(int i = 1; i <= que; i++)
if(ans[i]) puts("YES");
else puts("NO");
}
return 0;
}
标签:head,vst,CF1851G,int,点权,询问,define
From: https://www.cnblogs.com/C01et10n/p/17957921