[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<<],e[MN<<];
int f[MN],g[MN],siz[MN],ans[MN];
int n,m; inline int read()
{
int n=,f=; char c=getchar();
while (c<'' || c>'') {if(c=='-')f=-; c=getchar();}
while (c>='' && c<='') {n=n*+c-''; c=getchar();}
return n*f;
} rlt getrel(int x)
{
if (!f[x]) return (rlt){x,};
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>>;
if (qr<=mid) getins(l(x),L,mid,ql,qr,yl,yr);
else if (ql>mid) getins(r(x),mid+,R,ql,qr,yl,yr);
else getins(l(x),L,mid,ql,mid,yl,yr),getins(r(x),mid+,R,mid+,qr,yl,yr);
} void dfs(int x,int L,int R,int u)
{
register int i;
rlt xf,yf;
for (i=;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|=;}
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^;
d[x].push_back((node){xf.fa,yf.fa});
}
}
if (L==R) ans[L]=u;
else
{
int mid=L+R>>;
dfs(l(x),L,mid,u); dfs(r(x),mid+,R,u);
}
for (i=d[x].size()-;i>=;--i)
{
siz[d[x][i].x]-=siz[d[x][i].y];
f[d[x][i].y]=g[d[x][i].y]=;
}
} 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=;i<=m;++i) a[i].x=read(),a[i].y=read(),a[i].t=i;
sort(a+,a+m+,cmp);
for (i=;i<=m;)
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+=;
else getins(,,m,a[i].t,m,a[i].x,a[i].y),++i;
for (i=;i<=n;++i) siz[i]=;
dfs(,,m,);
for (i=;i<=m;++i) puts(ans[i]?"NO":"YES");
}
Last Word
推荐一波小D的《离线处理修改操作的分治算法(CDQ分治、线段树分治入门)》。
别找了,你找不到的。
每次打按秩合并的并查集总会忘记把初始大小赋为1,真是见鬼。
[Codeforces]813F Bipartite Checking的更多相关文章
- Bipartite Checking CodeForces - 813F (线段树按时间分治)
大意: 动态添边, 询问是否是二分图. 算是个线段树按时间分治入门题, 并查集维护每个点到根的奇偶性即可. #include <iostream> #include <sstream ...
- Codeforces 901C Bipartite Segments
Bipartite Segments 因为图中只存在奇数长度的环, 所以它是个只有奇数环的仙人掌, 每条边只属于一个环. 那么我们能把所有环给扣出来, 所以我们询问的区间不能包含每个环里的最大值和最小 ...
- Codeforces 901C Bipartite Segments(Tarjan + 二分)
题目链接 Bipartite Segments 题意 给出一个无偶环的图,现在有$q$个询问.求区间$[L, R]$中有多少个子区间$[l, r]$ 满足$L <= l <= r &l ...
- Codeforces 901C. Bipartite Segments(思维题)
擦..没看见简单环..已经想的七七八八了,就差一步 显然我们只要知道一个点最远可以向后扩展到第几个点是二分图,我们就可以很容易地回答每一个询问了,但是怎么求出这个呢. 没有偶数简单环,相当于只有奇数简 ...
- 【63.63%】【codeforces 724A】Checking the Calendar
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...
- Educational Codeforces Round 22 补题 CF 813 A-F
A The Contest 直接粗暴贪心 略过 #include<bits/stdc++.h> using namespace std; int main() {//freopen(&qu ...
- 【CodeForces】901 C. Bipartite Segments
[题目]C. Bipartite Segments [题意]给定n个点m条边的无向连通图,保证不存在偶数长度的简单环.每次询问区间[l,r]中包含多少子区间[x,y]满足只保留[x,y]之间的点和边构 ...
- CodeForces - 600F Edge coloring of bipartite graph
Discription You are given an undirected bipartite graph without multiple edges. You should paint the ...
- Codeforces Round #453 (Div. 1) 901C C. Bipartite Segments
题 http://codeforces.com/contest/901/problem/C codeforces 901C 解 首先因为图中没有偶数长度的环,所以: 1.图中的环长度全是奇数,也就是说 ...
随机推荐
- bzoj千题计划113:bzoj1023: [SHOI2008]cactus仙人掌图
http://www.lydsy.com/JudgeOnline/problem.php?id=1023 dp[x] 表示以x为端点的最长链 子节点与x不在同一个环上,那就是两条最长半链长度 子节点与 ...
- 第四篇:用IntelliJ IDEA 搭建基于jersey的RESTful api
编译器:Intellij IDEA 系统环境: MAC OS 相关技术:Maven.tomcat 7.jdk8 1.创建项目 首先创建一个web Application项目(这里我们打算用maven引 ...
- php析构方法
析构方法说明: 1. 析构方法会自动调用 2. 析构方法主要用于销毁资源(比如释放数据库的链接,图片资源...销毁某个对象..); 析构函数会在到对象的所有的引用都被删除或者当对象被显示销毁时执行. ...
- Java 持久化之 --io流与序列化操作
1)File类操作文件的属性 1.File类的常用方法 1. 文件的绝对完整路径:getAbsolutePath() 文件名:getName() 文件相对路径:getPath() 文件的上一级目录:g ...
- Mego开发文档 - 加载关系数据
加载关系数据 Mego允许您使用模型中的导航属性来加载相关数据对象.目前只支持强制加载数据对象.只有正确配置了关系才能加载关系数据,相关内容可参考关系配置文档. 加载对象属性 您可以使用该Includ ...
- mysql中的函数与存储过程
mysql中的函数:1 mysql下创建函数: 1.1 语法: delimiter $$ -- 设置分隔符,默认是; 设置成其他符号,让编译器知道我们函数编写的结束,此处设置成$$ create fu ...
- 日推20单词 Day03
1.occur v. 发生,发现 2.harvest n.收获,丰收 vt.收割,得到 3.crop n.庄稼,收成 4.yield n.产量 v.产出,屈服 5.field n.田野 6.featu ...
- 菜鸟容易中的招__setattr__
class Counter: def __init__(self): self.counter = 0 # 这里会触发 __setattr__ 调用 def __setattr__(self, nam ...
- iOS masonry 不规则tagView布局 并自适应高度
在搜索页面经常会有不规则的tag出现,这种tagView要有点击事件,单个tagView可以设置文字颜色,宽度不固定根据内容自适应,高度固定,数量不固定.总高度就不固定.最近对于masonry的使用又 ...
- maven引入本地jar 打jar包
没搭建私服的情况下引入本地的jar,并把本地jar打包进项目的run jar 以打包引入hadoop-common-2.7.5.jar为例 引用 复制jar包所在的路径 打开cmd命令提示符 切换路径 ...