[BZOJ4025]二分图(线段树分治,并查集)

4025: 二分图

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 2191  Solved: 800
[Submit][Status][Discuss]

Description

神犇有一个n个节点的图。因为神犇是神犇,所以在T时间内一些边会出现后消失。神犇要求出每一时间段内这个图是否是二分图。这么简单的问题神犇当然会做了,于是他想考考你。

Input

输入数据的第一行是三个整数n,m,T。

第2行到第m+1行,每行4个整数u,v,start,end。第i+1行的四个整数表示第i条边连接u,v两个点,这条边在start时刻出现,在第end时刻消失。

Output

输出包含T行。在第i行中,如果第i时间段内这个图是二分图,那么输出“Yes”,否则输出“No”,不含引号。

Sample Input

3 3 3
1 2 0 2
2 3 0 3
1 3 1 2

Sample Output

Yes
No
Yes

HINT

样例说明:

0时刻,出现两条边1-2和2-3。

第1时间段内,这个图是二分图,输出Yes。

1时刻,出现一条边1-3。

第2时间段内,这个图不是二分图,输出No。

2时刻,1-2和1-3两条边消失。

第3时间段内,只有一条边2-3,这个图是二分图,输出Yes。

数据范围:

n<=100000,m<=200000,T<=100000,1<=u,v<=n,0<=start<=end<=T。

Source

[Submit][Status][Discuss]

挺妙的思想,线段树不仅仅是一种数据结构,还是一种处理问题的思想,在图论的建图等方面也有用到。

这个方法全称应该叫“线段树对时间CDQ分治”,用于处理有时间参数的修改与询问问题。但是有一个前提,就是一个时间区间的修改可以分成互不影响的两个时间区间的修改,比如这题的边(u,v,start,end)可以分裂为(u,v,start,mid)和(u,v,mid+1,end)。

这题听说LCT可做,但还有一种思路,CDQ(L,R,A)表示询问在[L,R]中,关于这个询问(且还未被处理)的修改集合为A的分治过程。具体流程是:

1.遍历A,对于所有完全包含[L,R]的修改操作进行处理。

2.将A中所有不完全包含[L,R]的修改操作按照线段树的分法分到包含[L,mid]和[mid+1,R]的两个区间中,用集合B和C记录这些修改。

3.分别递归[L,mid]和[mid+1,R],如果L=R则可以直接特判回溯。

4.撤销本层所有操作,保证后续回溯与递归正确进行。

回到这一题,所谓修改就是记录距离的并查集操作(因为判断二分图的方法就是查找里面是否有奇环),所以这里的并查集必须支持撤销,不能路径压缩。总复杂度$O(n\log^2 n)$

 1 #include<cstdio>
 2 #include<vector>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=l; i<=r; i++)
 5 typedef long long ll;
 6 using namespace std;
 7
 8 const int N=200100;
 9 int n,m,T,u,v,l,r,top;
10 struct E{int u,v,l,r; bool operator<(const E &a)const{ return (l==a.l)?r<a.r:l<a.l; } };
11 vector<E> a;
12 struct P{ int fa,val,sz; }t[N];
13 struct D{ int x,y; P a,b; }stk[N];
14
15 void init(){ rep(i,1,n) t[i]=(P){i,0,1}; }
16 int find(int x){ while (t[x].fa!=x) x=t[x].fa; return x;}
17 int dis(int x){
18     int res=0;
19     while (t[x].fa!=x) res^=t[x].val,x=t[x].fa;
20     return res;
21 }
22
23 void link(int x,int y){
24     int val=dis(x)^dis(y)^1;
25     x=find(x); y=find(y);
26     stk[++top]=(D){x,y,t[x],t[y]};
27     if (t[x].sz>t[y].sz) swap(x,y);
28     t[x].fa=y; t[x].val=val; t[y].sz+=t[x].sz;
29 }
30
31 void recov(int k){ while (top!=k) t[stk[top].x]=stk[top].a,t[stk[top].y]=stk[top].b,top--; }
32
33 void CDQ(int L,int R,vector<E> &a){
34     int mid=(L+R)>>1,bot=top;
35     vector<E> b,c;
36     for (int i=0; i<(int)a.size(); i++){
37         E now=a[i]; int x=now.u,y=now.v;
38         if (now.l==L && now.r==R){
39             int p=find(x),q=find(y);
40             if (p==q){
41                 int val=dis(x)^dis(y);
42                 if (val==0){
43                     rep(i,L,R) puts("No");
44                     recov(bot); return;
45                 }
46             }else link(x,y);
47         }else if (now.r<=mid) b.push_back(now);
48             else if (now.l>mid) c.push_back(now);
49                 else b.push_back((E){now.u,now.v,now.l,mid}),c.push_back((E){now.u,now.v,mid+1,now.r});
50     }
51     if (L==R) puts("Yes"); else CDQ(L,mid,b),CDQ(mid+1,R,c);
52     recov(bot);
53 }
54
55 int main(){
56     freopen("bzoj4025.in","r",stdin);
57     freopen("bzoj4025.out","w",stdout);
58     scanf("%d%d%d",&n,&m,&T);
59     rep(i,1,m){
60         scanf("%d%d%d%d",&u,&v,&l,&r); l++;
61         if (l<=r) a.push_back((E){u,v,l,r});
62     }
63     init(); CDQ(1,T,a);
64     return 0;
65 }

原文地址:https://www.cnblogs.com/HocRiser/p/8727120.html

时间: 2024-11-01 14:55:38

[BZOJ4025]二分图(线段树分治,并查集)的相关文章

Codeforces 1140F 线段树 分治 并查集

题意及思路:https://blog.csdn.net/u013534123/article/details/89010251 之前cf有一个和这个相似的题,不过那个题只有合并操作,没有删除操作,直接并查集搞一搞就行了.对于这个题,因为有删除操作,我们对操作序列建一颗线段树,记录每个操作影响的区间操作就可以了.这里的并查集不能路径压缩,要按秩合并,这样复杂度是O(logn)的. 代码: #include <bits/stdc++.h> #define ls (o << 1) #de

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

Codeforces.1051G.Distinctification(线段树合并 并查集)

题目链接 \(Description\) 给定\(n\)个数对\(A_i,B_i\).你可以进行任意次以下两种操作: 选择一个位置\(i\),令\(A_i=A_i+1\),花费\(B_i\).必须存在一个位置\(j\),满足\(A_i=A_j,\ i\neq j\),才可以进行. 选择一个位置\(i\),令\(A_i=A_i-1\),花费\(-B_i\).必须存在一个位置\(j\),满足\(A_i=A_j+1\),才可以进行. 你需要对于所有\(i\in[1,n]\),求使得\(A_1,A_2,

算法学习——动态图连通性(线段树分治+按秩合并并查集)

在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集. 题目大意是这样的:有m个时刻,每个时刻有一个加边or撤销一条边的操作,保证操作合法,没有重边自环,每次操作后输出当前图下所有联通块大小的乘积. 首先观察到如果没有撤销操作,那么直接用并查集就可以维护,每次合并的时候乘上要合并的两个并查集大小的逆元,然后乘上合并之后的大小即可. 那么来考虑撤销,观察到如果并查集不带路径压缩,应该是可以处理撤销操作的. 但我们并不能直接做,因为并查集的撤销必须按顺序来,就相当于每次合并

BZOJ 4025 二分图 分治+并查集

题目大意:给定一张n个点的图,有m条边,T个时间段,每条边只存在于(st,ed]这些时间段,求每个时间段内这个图是否是二分图 分治并查集大法好 定义Solve(x,y,E)为当前处理的区间为[x,y],E为所有存在时间为[x,y]的子集的边的集合 那么对于E中的每一条边(u,v),讨论: 若当前边的存在时间为[x,y],则在并查集上判断是否出现奇环 如果出现,[x,y]内的所有时刻都一定不是二分图,输出答案即可 如果不出现,在并查集中连接(u,v) 否则判断存在时间和mid的关系讨论扔进左区间还

线段树分治总结

目录 类型一 例题1:八纵八横 代码: 例题2:时空旅行 首先,要求可以离线. 线段树分治有两种. 类型一 操作基于区间,单点询问. 有时,进行的一种操作可以快速完成,但是,要实现这种操作的逆操作较难. 因为,通常情况下,需要实现的逆操作都是很久以前执行的. 但是,如果只撤销上次操作,就会简单得多. 比如,维护一些连通性,或直径,线性基等问题. 这类问题加边很好做,但删边很难实现. 我们可以扫一遍操作,得到每个操作的有效区间. 然后,将每个添加操作的有效区间按在线段树上,然后遍历这颗线段树同时处

hdu 1829 &amp;amp;poj 2492 A Bug&amp;#39;s Life(推断二分图、带权并查集)

A Bug's Life Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 8528    Accepted Submission(s): 2745 Problem Description Background  Professor Hopper is researching the sexual behavior of a rare

POJ 2513 Colored Sticks(欧拉回路,字典树,并查集)

题意:给定一些木棒,木棒两端都涂上颜色,求是否能将木棒首尾相接,连成一条直线,要求不同木棒相接的一边必须是相同颜色的. 转:kuangbing 无向图存在欧拉路的充要条件为: ①     图是连通的: ②     所有节点的度为偶数,或者有且只有两个度为奇数的节点. 图的连通可以利用并查集去判断. 度数的统计比较容易. view code//第一次用指针写trie,本来是用二维数组,发现数组开不下,只好删删改改,改成指针 //做这道题,知道了欧拉回路判定,还有用指针写trie #include

【openjudge】C15C Rabbit&#39;s Festival CDQ分治+并查集

题目链接:http://poj.openjudge.cn/practice/C15C/ 题意:n 点 m 边 k 天.每条边在某一天会消失(仅仅那一天消失).问每一天有多少对点可以相互到达. 解法:开始不会做,参考的YYN的题解:http://blog.csdn.net/u013368721/article/details/45725181 学习了这种CDQ加并查集的做法,可以说是非常的巧妙了.复杂度可以保证在:O(KlogklogK)的范围. //CDQ + DSU #include <bit