@bzoj - 4025@ 二分图


@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

样例说明:
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。

@solution@

有在线 LCT 的做法和离线分治的做法,都很经典。
出于某些原因,我只写了分治的做法。
LCT 的后面来慢慢补(咕咕咕)

假如这个题只存在加边而不存在删边,我们应该怎么动态维护当前图是否为二分图?
二分图的定义是存在一个二染色的方案,我们不妨将当前的二染色方案维护出来。
当新加入一条边的两个端点在同一连通块时,查询它们的颜色是否相同,相同则不是二分图。
否则,我们需要合并两个连通块,可能还需要将某个连通块的颜色全部取反以保证它是二分图。

我们需要一个数据结构合并连通块,并且在合并的时候做整个连通块的修改。
并查集。可以在集合的代表元素处打 tag 再进行合并,访问某点时就可以将该点到集合代表元素(也就是根)的 tag 合并起来。

那么现在出现了删除操作,显然并查集上搞删除。。。那不就是 lct 干的事情吗。
但同时,我们也可以离线对时间分治回避删除的问题。
(其实我也不知道这算不算 cdq 分治。。。网上有说 cdq 分治必须要左区间对右区间影响,又有人说 cdq 分治其实就是时间分治。。。)

一条边存在的时间在时间轴上对应一个区间 (start, end]。为了方便,以下简写为 (s, e]
假如当前我们已经递归到时间轴上 [l, r],如果一条边的 (s, e] 完全包含 [l, r],则在此刻在并查集中加入该边。
判断此刻是否为二分图,如果不是,显然加入更多的边也不为二分图,直接将 [l, r] 中的答案改为 No。
否则继续往下递归。取中点 mid = (l + r)/2,分为左右两个小区间。将 [l, r] 没有处理的边如果跟左右区间有交集就递归传到左右两个区间,
如果递归到底,则可以判断该时刻的答案为 Yes。

该区间处理完毕,回撤并查集中在该区间 [l, r] 加入的边(注意并查集不能删除,但是能回撤)。
注意我们要回撤并查集信息,所以要写按秩合并的并查集。

事实上这样的递归结构就类似于线段树,每一条边的 (s, e] 被拆成线段树上 log n 个点,线段树的底部结点就是时间轴的每一时刻。
我们分治的过程相当于自上而下遍历该线段树,将底部结点到线段树的根上所有信息合并处理出该时刻对应的图,进行判断。遍历返回时将结点信息回撤。
直接改 No 理性理解一下,大概就是在剪枝。

具体。。。看代码?

@accepted code@

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 100000 + 5;
const int MAXM = 200000 + 5;
const int MAXT = 100000 + 5;
struct edge{
	int u, v, s, e;
	edge(int _u=0, int _v=0, int _s=0, int _e=0):u(_u), v(_v), s(_s), e(_e){}
};
int fa[MAXN], rnk[MAXN], d[MAXN];
int find(int x) {
	return (fa[x] == x) ? x : find(fa[x]);
}
int dis(int x) {
	return (fa[x] == x) ? 0 : d[x]^dis(fa[x]);
}
struct modify{
	int x, y; bool b;
}stk[MAXN];
int tp = 0;
bool unite(int x, int y) {
	int fx = find(x), fy = find(y);
	if( fx == fy ) return dis(x)^dis(y);
	else {
		if( rnk[fx] > rnk[fy] ) swap(fx, fy);
		d[fx] = dis(x)^dis(y)^1;
		tp++; stk[tp].x = fx; stk[tp].y = fy;
		if( rnk[fx] == rnk[fy] ) rnk[fy]++, stk[tp].b = true;
		else stk[tp].b = false;
		fa[fx] = fy; 
		return true;
	}
}
void restore(int x) {
	while( tp != x ) {
		fa[stk[tp].x] = stk[tp].x, d[stk[tp].x] = 0;
		if( stk[tp].b ) rnk[stk[tp].y]--;
		tp--;
	}
}
bool ans[MAXT]; int n, m, T;
void solve(int L, int R, vector<edge>e) {
	int nw = tp, mid = (L + R) >> 1;
	vector<edge>el, er;
	for(int i=0;i<e.size();i++) {
		if( e[i].s <= L && R <= e[i].e ) {
			if( !unite(e[i].u, e[i].v) ) {
				for(int j=L;j<=R;j++)
					ans[j] = false;
				restore(nw);
				return ;
			}
		}
		else {
			if( e[i].s <= mid && e[i].e >= L ) el.push_back(e[i]);
			if( e[i].e > mid && e[i].s <= R ) er.push_back(e[i]);
		}
	}
	if( L == R ) ans[L] = true;
	else solve(L, mid, el), solve(mid + 1, R, er);
	restore(nw);
}
int main() {
	scanf("%d%d%d", &n, &m, &T);
	for(int i=1;i<=n;i++)
		fa[i] = i, d[i] = 0, rnk[i] = 1;
	vector<edge>edges;
	for(int i=1;i<=m;i++) {
		int u, v, s, e;
		scanf("%d%d%d%d", &u, &v, &s, &e), s++;
		edges.push_back(edge(u, v, s, e));
	}
	solve(1, T, edges);
	for(int i=1;i<=T;i++)
		printf("%s\n", ans[i] ? "Yes" : "No");
}

@details@

康复计划 - 6。

事实上只是为了接下来一道数据结构毒瘤题练练手,熟悉一下这个套路。
结果就做了一个上午。。。
我果然还是准备准备 AFO 好了。。。

posted @ 2019-06-25 11:46  Tiw_Air_OAO  阅读(158)  评论(0编辑  收藏  举报