AtCoder AGC014E Blue and Red Tree (启发式合并)

题目链接

https://atcoder.jp/contests/agc014/tasks/agc014_e

题解

完了考场上树剖做法都没想到是不是可以退役了。。。

首先有一个巨难写的据说是\(O(n\log^3n)\)的树剖+树套树做法:
对于每条红边\((u,v)\), 给蓝树上两点间路径\(+1\), 然后每次选出一个值为\(1\)的边,找到覆盖它的红边然后把这条\(1\)的边断掉加上红边,再去掉红边的影响。

下面来说正解。
依然是上面的思路,然后发现假设断掉前\((i-1)\)条蓝边之后形成的联通块的集合是\(B_i\), 连上从第\(i\)条到第\(n\)条的红边之后形成的连通块集合是\(R_i\), 那么答案为YES当且仅当对于任意\(2\le i\le n\), \(R_i=B_i\).
于是可以得到如下转化: 一开始图上有\(n\)个点\(2(n-1)\)条边,若两条边重合则把其连接的两点缩成同一点,问整个图最后能不能缩成一个点。
启发式合并即可。维护目前所有重边的队列、每个点的相邻点以及图上所有的边。每次从队列里取出一条边,然后把度数较小的点的边全部转移到度数较大的点上。如果能如此重复做\((n-1)\)次,那么答案是YES, 否则为NO. 相邻点可以用set维护,图上边可以用map维护。
时间复杂度\(O(n\log^2n)\).

代码

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cassert>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<utility>
#define pii pair<int,int> 
#define mkpr make_pair
using namespace std;

const int N = 1e5;
multiset<int> ae[N+3];
map<pii,int> g;
queue<pii> que;
int n;

void insertedge(int u,int v)
{
    if(u==v) return;
    if(u>v) swap(u,v);
    ae[u].insert(v); ae[v].insert(u);
    g[mkpr(u,v)]++;
    if(g[mkpr(u,v)]==2) {que.push(mkpr(u,v));}
}

void deleteedge(int u,int v)
{
    ae[v].erase(ae[v].find(u));
    if(u>v) swap(u,v);
    if(g.count(mkpr(u,v))) {g.erase(mkpr(u,v));}
}

int main()
{
    scanf("%d",&n);
    for(int i=1; i<=2*(n-1); i++)
    {
        int u,v; scanf("%d%d",&u,&v);
        insertedge(u,v);
    }
    for(int i=1; i<n; i++)
    {
        while(1)
        {
            if(que.empty()) {puts("NO"); return 0;}
            int u = que.front().first,v = que.front().second; que.pop();
            if(!g.count(mkpr(u,v))) {continue;}
            if(ae[u].size()<ae[v].size()) swap(u,v);
            for(set<int>::iterator i=ae[v].begin(); i!=ae[v].end(); i++)
            {
                int x = *i;
                deleteedge(v,x);
                insertedge(u,x);
            }
            break;
        }
    }
    puts("YES");
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/suncongbo/p/11527505.html