Codeforces 892E (可撤销按秩合并并查集+MST)

题目链接

题意

给一张$n$个点$m$条边的无向连通图,有$q$次询问,每次给出一些边,让你判断这些边能否同时出现在一颗最小生成树上

题解

  • 首先要知道的一个性质就是一个无向连通图的最小生成树权值不同边的选择是相互独立的
  • 所以我们可以把询问离线按边权从小到大排序,再按$Kruskal$的做法加边求最小生成树
  • 如果当前加的边的权值等于询问的权值时,先对每个询问的边判断边上的两个点是否在同一连通块里,如果在就说明当前边不能加入最小生成树里,那么对应询问的答案就为$NO$
  • 对于在同一询问里边权相同的边,因为要同时加入最小生成树,所以在询问的过程中把对应的边加入最小生成树中,然后用可撤销并查集回退到加边之前就行了
  • 因为要求并查集可撤销操作所以不能用路径压缩只能写按秩合并复杂度$O(mlogn)$
查看代码
#include <bits/stdc++.h>
using namespace std;
#define _for(i, a, b) for (int i = (a); i <= (b); ++i)
typedef long long ll;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
struct DSU
{
    int pre[maxn], h[maxn], top;
    struct Node
    {
        int x, y, fa, h;
        Node(int x = 0, int y = 0, int fa = 0, int h = 0) : x(x), y(y), fa(fa), h(h) {}
    } stk[maxn];
    void init(int n)
    {
        top = 0;
        for (int i = 1; i <= n; ++i)
            pre[i] = i, h[i] = 0;
    }
    int Find(int x)
    {
        return pre[x] == x ? x : Find(pre[x]);
    }
    void merge(int u, int v)
    {
        int x = Find(u), y = Find(v);
        if (x == y)
            return;
        if (h[x] > h[y])
            swap(x, y);
        stk[top++] = Node(x, y, pre[x], h[y]);
        if (h[x] == h[y])
            h[y]++; //高度++
        pre[x] = y;
    }
    void revoc(int k)
    {
        for (int i = 0; i < k; ++i)
        {
            Node &it = stk[--top];
            pre[it.x] = it.fa;
            h[it.y] = it.h;
        }
    }
} dsu;

struct EDGE
{
    int s, t, cost;
    EDGE(int s = 0, int t = 0, int cost = 0) : s(s), t(t), cost(cost) {}
    friend bool operator<(EDGE a, EDGE b)
    {
        return a.cost < b.cost;
    }
} edge[maxn];

struct Query
{
    int s, t, qid, cost;
    Query(int s = 0, int t = 0, int q = 0, int c = 0) : s(s), t(t), qid(q), cost(c) {}
    friend bool operator<(Query a, Query b)
    {
        return a.cost == b.cost ? a.qid < b.qid : a.cost < b.cost;
    }
} q[maxn];
bool ans[maxn];
int main()
{
#ifndef ONLINE_JUDGE
    freopen("simple.in", "r", stdin);
    freopen("simple.out", "w", stdout);
#endif
    memset(ans, 1, sizeof(ans));
    int n, m, p;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i)
    {
        scanf("%d%d%d", &edge[i].s, &edge[i].t, &edge[i].cost);
    }
    scanf("%d", &p);
    int tot = 0;
    for (int i = 1, a, b; i <= p; ++i)
    {
        scanf("%d", &a);
        for (int j = 1; j <= a; ++j)
        {
            scanf("%d", &b);
            q[++tot] = Query(edge[b].s, edge[b].t, i, edge[b].cost);
        }
    }
    sort(edge + 1, edge + 1 + m);
    sort(q + 1, q + 1 + tot);
    dsu.init(n);
    int now = 1;
    for (int i = 1; i <= m; ++i)
    {
        int cnt = 0;
        while (q[now].cost == edge[i].cost)
        {
            if (q[now - 1].cost == q[now].cost && q[now].qid != q[now - 1].qid)
                dsu.revoc(cnt), cnt = 0;
            int x = dsu.Find(q[now].s), y = dsu.Find(q[now].t);
            if (x == y)
            {
                ans[q[now].qid] = 0;
            }
            else
                dsu.merge(x, y), cnt++;
            now++;
        }
        dsu.revoc(cnt);
        dsu.merge(edge[i].s, edge[i].t);
    }
    for (int i = 1; i <= p; ++i)
    {
        if (ans[i])
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}
posted @ 2020-06-04 15:39  tryatry  阅读(248)  评论(0编辑  收藏  举报