洛谷P4185 [USACO18JAN]MooTube G 题解

题意简述

给定一颗带边权无根树,定义两个点之间的关联度是两点间最短路径的边权最小值

多次询问,每次询问 \(v\)\(k\),求有多少个点满足与 \(v\) 的关联度不小于 \(k\)

解题报告

首先想一个不是很暴力的暴力

对于某一个 \(k\),因为关联度是边权最小值,所以只加入合法的边(边权 \(\geq k\))建一个不一定联通的图,就能删掉不满足的点对。此时图中任意两个联通的点之间都满足关联度 \(\geq k\),统计 \(v\) 所在连通块大小就能求出答案。

每次要扫一遍所有的边,维护连通性可以用并查集,时间复杂度 \(O(qn\log n)\)


发现加边这个过程其实不需要每次都扫一遍

其实随着 \(k\) 的逐渐上升,以前加过的边还是会被加,只是会多一些新的边

于是可以将询问离线下来按 \(k\) 排序,把边按边权排序,然后处理询问的时候每次只加需要的边

时间复杂度 \(O(n + q\log n)\)

代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>

#define forall(G,i) for (int i = 0, __for_siz__ = (int) (G).size(); i < __for_siz__; ++i) 
#define DEBUG(x) std::cerr << #x << " = " << x << std::endl;
#define ALL(x) (x).begin(), (x).end()
#define MP std::make_pair
#define se second
#define fi first

using std::cin;
using std::cout;
using std::endl;

inline int read() {
    int s = 0, x = 1; char ch = getchar();
    while (!isdigit(ch)) { if (ch == '-') x = -x; ch = getchar(); }
    while (isdigit(ch)) { s = s * 10 + ch - '0'; ch = getchar(); }
    return s * x;
}

const int MAXN = 100000 + 10;

// 对于一个 k,某点周围所有满足的点都要有 w <= k
// 因此使用并查集维护连通性,对于每个询问分别加入所有 w <= k 的边
// 然后发现 k 在单调增的时候可以动态更新边集

int n, q;

struct REdge {
    int u, v, w;

    REdge(int _u = 0, int _v = 0, int _w = 0) :
        u(_u), v(_v), w(_w) {}
} es[MAXN], qry[MAXN];

bool cmp1(REdge r1, REdge r2) {
    return r1.w > r2.w;
}
bool cmp2(REdge r1, REdge r2) {
    return r1.w < r2.w;
}

struct DSU {
    int u[MAXN]; int siz[MAXN];

    void Init(int n) {
        for (int i = 1; i <= n; ++i) siz[i] = 1;
    }
    int Find(int x) { return !u[x] ? x : u[x] = Find(u[x]); }
    int Size(int x) { return siz[Find(x)];}
    bool Merge(int x, int y) {
        x = Find(x); y = Find(y);
        if (x == y) return false;
        u[x] = y; siz[y] += siz[x];
        siz[x] = 0; return true;
    }
} U;

int anss[MAXN];

int main() {
    n = read(); q = read();
    U.Init(n);
    for (int i = 1; i <= n - 1; ++i) {
        int u = read(); int v = read(); int w = read();
        es[i] = REdge(u, v, w);
    }
    for (int i = 1; i <= q; ++i) {
        int k = read(); int u = read();
        qry[i] = REdge(u, i, k);
    } std::sort(es + 1, es + 1 + n - 1, cmp1);
    std::sort(qry + 1, qry + 1 + q, cmp1);

    int ii = 1;
    
    for (int iq = 1; iq <= q; ++iq) {
        int k = qry[iq].w; 
        for (; ii <= n - 1; ++ii) {
            if (es[ii].w >= k) {
                U.Merge(es[ii].u, es[ii].v);
            } else break;
        }
        anss[qry[iq].v] = U.Size(qry[iq].u);
    }
    for (int i = 1; i <= q; ++i) printf("%d\n", anss[i] - 1);
    return 0;
}
posted @ 2021-04-27 16:20  Handwer  阅读(52)  评论(0编辑  收藏  举报