CF1851G

发布时间 2024-01-11 10:02:06作者: C01et10n

诸位大佬把思路讲的很清晰了,我主要补充一下实现。

思路

考虑:如果一个询问的答案是肯定的,它对路径上所有点的要求。

询问为 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))\)

Submission

//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;
}