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

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)。

  1. #include <cstdio>
  2. #include <vector>
  3. #include <algorithm>
  4. #define MN 100005
  5. #define l(a) (a<<1)
  6. #define r(a) (a<<1|1)
  7. using namespace std;
  8. struct node{int x,y;};
  9. struct rlt{int fa,rel;};
  10. struct meg{int x,y,t;}a[MN];
  11. vector <node> d[MN<<],e[MN<<];
  12. int f[MN],g[MN],siz[MN],ans[MN];
  13. int n,m;
  14.  
  15. inline int read()
  16. {
  17. int n=,f=; char c=getchar();
  18. while (c<'' || c>'') {if(c=='-')f=-; c=getchar();}
  19. while (c>='' && c<='') {n=n*+c-''; c=getchar();}
  20. return n*f;
  21. }
  22.  
  23. rlt getrel(int x)
  24. {
  25. if (!f[x]) return (rlt){x,};
  26. rlt lt=getrel(f[x]);
  27. return (rlt) {lt.fa,lt.rel^g[x]};
  28. }
  29.  
  30. void getins(int x,int L,int R,int ql,int qr,int yl,int yr)
  31. {
  32. if (ql==L&&qr==R) {e[x].push_back((node){yl,yr}); return;}
  33. int mid=L+R>>;
  34. if (qr<=mid) getins(l(x),L,mid,ql,qr,yl,yr);
  35. else if (ql>mid) getins(r(x),mid+,R,ql,qr,yl,yr);
  36. else getins(l(x),L,mid,ql,mid,yl,yr),getins(r(x),mid+,R,mid+,qr,yl,yr);
  37. }
  38.  
  39. void dfs(int x,int L,int R,int u)
  40. {
  41. register int i;
  42. rlt xf,yf;
  43. for (i=;i<e[x].size();++i)
  44. {
  45. xf=getrel(e[x][i].x); yf=getrel(e[x][i].y);
  46. if (xf.fa==yf.fa) {if (xf.rel==yf.rel) u|=;}
  47. else
  48. {
  49. if (siz[xf.fa]<siz[yf.fa]) swap(xf,yf);
  50. siz[xf.fa]+=siz[yf.fa];
  51. f[yf.fa]=xf.fa;
  52. g[yf.fa]=xf.rel^yf.rel^;
  53. d[x].push_back((node){xf.fa,yf.fa});
  54. }
  55. }
  56. if (L==R) ans[L]=u;
  57. else
  58. {
  59. int mid=L+R>>;
  60. dfs(l(x),L,mid,u); dfs(r(x),mid+,R,u);
  61. }
  62. for (i=d[x].size()-;i>=;--i)
  63. {
  64. siz[d[x][i].x]-=siz[d[x][i].y];
  65. f[d[x][i].y]=g[d[x][i].y]=;
  66. }
  67. }
  68.  
  69. bool cmp(const meg& a,const meg& b)
  70. {
  71. if (a.x!=b.x) return a.x<b.x;
  72. if (a.y!=b.y) return a.y<b.y;
  73. return a.t<b.t;
  74. }
  75.  
  76. int main()
  77. {
  78. register int i;
  79. n=read(); m=read();
  80. for (i=;i<=m;++i) a[i].x=read(),a[i].y=read(),a[i].t=i;
  81. sort(a+,a+m+,cmp);
  82. for (i=;i<=m;)
  83. if (a[i].x==a[i+].x&&a[i].y==a[i+].y) getins(,,m,a[i].t,a[i+].t-,a[i].x,a[i].y),i+=;
  84. else getins(,,m,a[i].t,m,a[i].x,a[i].y),++i;
  85. for (i=;i<=n;++i) siz[i]=;
  86. dfs(,,m,);
  87. for (i=;i<=m;++i) puts(ans[i]?"NO":"YES");
  88. }

Last Word

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

  别找了,你找不到的。

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

[Codeforces]813F Bipartite Checking的更多相关文章

  1. Bipartite Checking CodeForces - 813F (线段树按时间分治)

    大意: 动态添边, 询问是否是二分图. 算是个线段树按时间分治入门题, 并查集维护每个点到根的奇偶性即可. #include <iostream> #include <sstream ...

  2. Codeforces 901C Bipartite Segments

    Bipartite Segments 因为图中只存在奇数长度的环, 所以它是个只有奇数环的仙人掌, 每条边只属于一个环. 那么我们能把所有环给扣出来, 所以我们询问的区间不能包含每个环里的最大值和最小 ...

  3. Codeforces 901C Bipartite Segments(Tarjan + 二分)

    题目链接  Bipartite Segments 题意  给出一个无偶环的图,现在有$q$个询问.求区间$[L, R]$中有多少个子区间$[l, r]$ 满足$L <= l <= r &l ...

  4. Codeforces 901C. Bipartite Segments(思维题)

    擦..没看见简单环..已经想的七七八八了,就差一步 显然我们只要知道一个点最远可以向后扩展到第几个点是二分图,我们就可以很容易地回答每一个询问了,但是怎么求出这个呢. 没有偶数简单环,相当于只有奇数简 ...

  5. 【63.63%】【codeforces 724A】Checking the Calendar

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  6. Educational Codeforces Round 22 补题 CF 813 A-F

    A The Contest 直接粗暴贪心 略过 #include<bits/stdc++.h> using namespace std; int main() {//freopen(&qu ...

  7. 【CodeForces】901 C. Bipartite Segments

    [题目]C. Bipartite Segments [题意]给定n个点m条边的无向连通图,保证不存在偶数长度的简单环.每次询问区间[l,r]中包含多少子区间[x,y]满足只保留[x,y]之间的点和边构 ...

  8. CodeForces - 600F Edge coloring of bipartite graph

    Discription You are given an undirected bipartite graph without multiple edges. You should paint the ...

  9. Codeforces Round #453 (Div. 1) 901C C. Bipartite Segments

    题 http://codeforces.com/contest/901/problem/C codeforces 901C 解 首先因为图中没有偶数长度的环,所以: 1.图中的环长度全是奇数,也就是说 ...

随机推荐

  1. android使用sharesdk的小感

    1.sharesdk快捷方式,快捷方式集成了所有需要分享到的手机app,但是具有缺陷,举个例子(想要微信分享图片url,但是短信并不想带有图片,否则短信成彩信,这里集成的就有麻烦了,为了解决这种问题, ...

  2. JAVA的循环控制与循环嵌套

    循环控制和循环嵌套 循环控制是除了循环条件之外,控制循环是否进行的一个机制,这给处理循环问题带来了灵活性.循环体内的语句块可以是顺序执行的语句,可以是分支结构的语句,也可以是循环语句,循环中含循环,就 ...

  3. EVA 4400存储硬盘故障数据恢复方案和数据恢复过程

    EVA系列存储是一款以虚拟化存储为实现目的的HP中高端存储设备,平时数据会不断的迁移,加上任务通常较为繁重,所以磁盘的负载相对是较重的,也是很容易出现故障的.EVA是依靠大量磁盘的冗余空间,以及故障后 ...

  4. day-3 python多线程编程知识点汇总

    python语言以容易入门,适合应用开发,编程简洁,第三方库多等等诸多优点,并吸引广大编程爱好者.但是也存在一个被熟知的性能瓶颈:python解释器引入GIL锁以后,多CPU场景下,也不再是并行方式运 ...

  5. prop attr 到底哪里不一样?

    好吧 首先承认错误  说好的每天进行一次只是总结  但是我没坚持住 准确的来说 我并没有每天会学到了东西 但是 我一直在持续努力着  以后应该不会每天都写  但是自己觉得有用的  或者想加强记忆的 可 ...

  6. kali rolling更新源之gpg和dirmngr问题

    1.编辑 /etc/apt/source.list gedit /etc/apt/sources.list 输入更新源,可以选任何可用更新源,这里设置官方源 deb http://http.kali. ...

  7. js判断操作系统windows,ios,android(笔记)

    使用JS判断用户使用的系统是利用浏览器的userAgent. navigator.userAgent:userAgent 获取了浏览器用于 HTTP 请求的用户代理头的值. navigator.pla ...

  8. 第四章 Ajax与jQuery

    第四章   Ajax与jQuery 一.Ajax简介 在传统的Web应用中,每次请求服务器都会生成新的页面,用户在提交请求后,总是要等待服务器的响应.如果前一个请求没有响应,则后一个请求就不能发送,在 ...

  9. Python内置函数(27)——range

    英文文档: range(stop) range(start, stop[, step]) Rather than being a function, range is actually an immu ...

  10. vueJs 源码解析 (三) 具体代码

    vueJs 源码解析 (三) 具体代码 在之前的文章中提到了 vuejs 源码中的 架构部分,以及 谈论到了 vue 源码三要素 vm.compiler.watcher 这三要素,那么今天我们就从这三 ...