zoukankan      html  css  js  c++  java
  • [Codeforces]813F Bipartite Checking

      往期题目补档。既然被选为了经典题就拿来写一写。

    Description

      给定一张含有n个点的无向图,一开始图中没有任何边。依次给出q次操作,每次操作给出两个点“x y”,若x和y之间没有边相连,则连上这条边,否则移除这条边。对于每次操作,你都要判断执行这一次操作之后,整张图是否为二分图。

    Input

      第一行两个正整数n,q,表示点数和操作数。
      接下来q行,每行两个正整数“x y”表示操作。

    Output

      对于每次操作,判断执行该操作后,整张图是否为二分图,若为二分图,输出“YES”,否则输出“NO”。

    Sample Input

      3 5
      2 3
      1 3
      1 2
      1 2
      1 2

    Sample Output

      YES
      YES
      NO
      YES
      NO

    HINT

      2 <= n,q <= 100000,1 <= x < y <= n。

    Solution

      Link Cut 二分图问题??

      然而我似乎知道只支持加边的判断二分图有一个绝妙的算法,然而怎么做到删除?

      实际上,我们换一个想法,就可以通过离线处理把删除转化为撤销。

      我们的每次询问实际上都是对图的一个状态进行询问,我们把操作序列看作一个时间戳。

      如样例,在第一时刻,图中的边有(2,3);第二时刻和第四时刻,图中的边有(2,3)、(1,3);第三时刻有(2,3)、(1,3)、(1,2)……

      最最朴素的想法,对于每个时刻,我们把该时刻存在的边全部插入,判断答案,然后清空,进行下一时刻的统计。

      这样的操作数显然是q^2的,但我们注意到某些边存在的时间是连续的一段区间,似乎不需要频繁地插入撤销?

      于是我们就有了线段树分治。

      我们按时间戳开一个线段树,然后把每条边按照存在的时间段丢进线段树里。

      每条边确确实实是被“丢”进线段树里的,找到该时间段在线段树里对应的至多log个区间,把这条边也就是这个操作存起来而已。

      每条边每进行一对插入和删除操作,就会产生一段时间段;对于直到q时刻还存在于图中的边,我们认为它们在q+1时刻被删除了。

      这样的操作数是qlogq的,也就是说我们用一个log的时间代价将删除操作变为撤销操作。

      这样我们已经完成了核心的分治操作。

      剩下的我们只要将这棵线段树dfs一遍,每到一个结点,把该结点中存储的操作加入,离开的时候撤销掉这些操作,在底层计算答案即可。

      撤销一般有两种方式,一种是存储父结点的状态,另一种是执行该操作的逆操作。

      说了这么多,这一题该如何在支持加边操作的情况下判断二分图呢?

      由二分图染色的思想,我们有一种带权并查集的做法。

      对于一张二分图内的一个联通块,一旦点A相对于点B的颜色确定,那么点A相对于该联通块内的其他点的颜色都能确定。

      于是我们在并查集内除了维护父亲是谁,还要维护它相对于父亲的颜色(相同或相异)。

      当一张图加入了这样一条边之后,它就不再是二分图:这条边连接了一个联通块内颜色相同的结点。

      至于撤销,显然不能存储父结点的状态,只能执行逆操作,所以我们要用到并查集的按秩合并以支持撤销。

      总时间复杂度O(qlogqlogn)。

    #include <cstdio>
    #include <vector>
    #include <algorithm>
    #define MN 100005
    #define l(a) (a<<1)
    #define r(a) (a<<1|1)
    using namespace std;
    struct node{int x,y;};
    struct rlt{int fa,rel;};
    struct meg{int x,y,t;}a[MN];
    vector <node> d[MN<<2],e[MN<<2];
    int f[MN],g[MN],siz[MN],ans[MN];
    int n,m;
    
    inline int read()
    {
        int n=0,f=1; char c=getchar();
        while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
        while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
        return n*f;
    }
    
    rlt getrel(int x)
    {
        if (!f[x]) return (rlt){x,0};
        rlt lt=getrel(f[x]);
        return (rlt) {lt.fa,lt.rel^g[x]};
    }
    
    void getins(int x,int L,int R,int ql,int qr,int yl,int yr)
    {
        if (ql==L&&qr==R) {e[x].push_back((node){yl,yr}); return;}
        int mid=L+R>>1;
        if (qr<=mid) getins(l(x),L,mid,ql,qr,yl,yr);
        else if (ql>mid) getins(r(x),mid+1,R,ql,qr,yl,yr);
        else getins(l(x),L,mid,ql,mid,yl,yr),getins(r(x),mid+1,R,mid+1,qr,yl,yr);
    }
    
    void dfs(int x,int L,int R,int u)
    {
        register int i;
        rlt xf,yf;
        for (i=0;i<e[x].size();++i)
        {
            xf=getrel(e[x][i].x); yf=getrel(e[x][i].y);
            if (xf.fa==yf.fa) {if (xf.rel==yf.rel) u|=1;}
            else
            {
                if (siz[xf.fa]<siz[yf.fa]) swap(xf,yf);
                siz[xf.fa]+=siz[yf.fa];
                f[yf.fa]=xf.fa;
                g[yf.fa]=xf.rel^yf.rel^1;
                d[x].push_back((node){xf.fa,yf.fa});
            }
        }
        if (L==R) ans[L]=u;
        else
        {
            int mid=L+R>>1;
            dfs(l(x),L,mid,u); dfs(r(x),mid+1,R,u);
        }
        for (i=d[x].size()-1;i>=0;--i)
        {
            siz[d[x][i].x]-=siz[d[x][i].y];
            f[d[x][i].y]=g[d[x][i].y]=0;
        }
    }
    
    bool cmp(const meg& a,const meg& b)
    {
        if (a.x!=b.x) return a.x<b.x;
        if (a.y!=b.y) return a.y<b.y;
        return a.t<b.t;
    }
    
    int main()
    {
        register int i;
        n=read(); m=read();
        for (i=1;i<=m;++i) a[i].x=read(),a[i].y=read(),a[i].t=i;
        sort(a+1,a+m+1,cmp);
        for (i=1;i<=m;)
            if (a[i].x==a[i+1].x&&a[i].y==a[i+1].y) getins(1,1,m,a[i].t,a[i+1].t-1,a[i].x,a[i].y),i+=2;
            else getins(1,1,m,a[i].t,m,a[i].x,a[i].y),++i;
        for (i=1;i<=n;++i) siz[i]=1;
        dfs(1,1,m,0);
        for (i=1;i<=m;++i) puts(ans[i]?"NO":"YES");
    }

    Last Word

      推荐一波小D的《离线处理修改操作的分治算法(CDQ分治、线段树分治入门)》。

      别找了,你找不到的。

      每次打按秩合并的并查集总会忘记把初始大小赋为1,真是见鬼。

  • 相关阅读:
    z-index坑
    一些常用的可以封装好的方法
    echarts线状图
    vue 用js复制内容
    Java并发系列
    ThreadLocal讲解
    TreeMap源码学习
    HashMap源码学习
    Java Socket编程
    socket、tcp、udp、http 的认识及区别
  • 原文地址:https://www.cnblogs.com/ACMLCZH/p/7902082.html
Copyright © 2011-2022 走看看