bzoj4025 二分圖 線段樹分治+帶權可回滾並查集+前向星

題目鏈接:傳送門

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”,不含引號。

二分圖判定

引理:若一個圖是二分圖,則這個圖不存在奇環。

比較好證明,珂以看這篇博客,這裏不證了qwq
先不考慮邊消失的情況,考慮怎樣判二分圖:想到用帶權並查集,帶權並查集上維護點到代表元的距離的奇偶性。
加入一條邊(u,v)(u,v)時分情況討論:
如果uuvv本來不在同一個聯通塊中,則連邊也不珂能形成環,所以直接合並就好qwq
合併時是代表元合併,假設xx到代表元的距離的奇偶性爲dis[x]dis[x]00表示偶數,11表示奇數。
代表元之間的距離應爲dis[u]dis[u] ^ dis[v]dis[v] ^ 11(^表示異或),表示uu到代表元的距離加上vv到代表元的距離加上1的奇偶性。
如果uuvv在同一個聯通塊中,且uuvv到代表元距離之和爲偶數,則連邊之後會形成奇環(加上了新的邊,偶數變奇數)

解析

如果邊出現的時間段不相交,發現每次消失的邊都是最後加入的qwq。
那麼珂以用資磁回滾的並查集,搞一個棧,像回滾莫隊一樣回滾。
但是這裏邊出現的時間段珂以相♂交,所以考慮把所有線段分成若干不相♂交的線段。
線段?線段樹!
首先把每個時間段對應到線段樹上的若干區間。
舉個栗子,假設T=4T=4,即所有線段都在[1,4][1,4]內,然後有一條邊出現的時間是[1,3][1,3]
腦補一下,珂以發現這裏會把它映射到[1,2][1,2][3,3][3,3]這兩個區間上qwq。
意思就是讓它在11時刻出現,在22時刻末消失,再在33時刻出現,33時刻末消失。
這裏用前向星存下線段樹上每個節點被哪些邊包含(用vector也珂以qwq)
用一次詢問處理出所有答案:
每次訪問一個線段樹節點,先把前向星裏的所有儲存的邊加到圖中,然後判是不是二分圖。
如果不是,那麼再加入邊這個圖也不會成爲二分圖,所以這個時間段內圖均不是二分圖,所以回滾後返回。
如果是,葉子節點的情況就珂以記錄答案了,否則遞歸求解兩個孩子。

Talk is cheap, show you the code:

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
#define rl register ll
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<1)+(x<<3)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=200005;
const int INF=0x3f3f3f3f;
namespace UnionFindSet {

int father[Size],siz[Size],dis[Size];
inline void init(int n) {
	for(re i=1; i<=n; i++) {
		father[i]=i;
		siz[i]=1;
		dis[i]=0;
	}
}
int Find(int x) {
	if(x==father[x])	return x;
	return Find(father[x]);
}
struct Stack {
	int top;
	int u[Size],v[Size],fa[Size],sz[Size],d[Size];
	inline void push(int x,int y) {
		u[++top]=x;
		v[top]=y;
		fa[top]=father[x];
		sz[top]=siz[y];
		d[top]=dis[x];
	}
	inline void pop() {
		father[u[top]]=fa[top];
		siz[v[top]]=sz[top];
		dis[u[top]]=d[top];
		top--;
	}
	void pop(int x) {
		while(top>x) {
			pop();
		}
	}
} S;
void Union(int u,int v) {
	re fu=Find(u);
	re fv=Find(v);
	if(siz[fu]>siz[fv])	swap(fu,fv);
	S.push(fu,fv);
	siz[fv]+=siz[fu];
	dis[fu]=dis[u]^dis[v]^1;
	father[fu]=fv;
}
int dist(int x) {
	if(x==father[x])	return dis[x];
	return dist(father[x])^dis[x];
}

}
using namespace UnionFindSet;
int n,m,T,cnt,head[Size<<2];
struct Edge {		//用前向星存下包括線段樹上某個節點的區間的線段 
	int v,next;
} w[Size*25];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
struct node {
	int l,r;
} tree[Size<<2];
void Build(int l,int r,int rt) {
	tree[rt].l=l;
	tree[rt].r=r;
	if(l==r)	return;
	int mid=(l+r)>>1;
	Build(l,mid,lc);
	Build(mid+1,r,rc);
}
void Update(int l,int r,int x,int rt) {
	if(l<=tree[rt].l && tree[rt].r<=r) {
		//如果這條邊的編號和線段樹節點的編號連邊 
		//說明這條邊出現的時間包含了這個節點的左右區間 
		AddEdge(rt,x);
		return;
	}
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(l<=mid)	Update(l,r,x,lc);
	if(r>mid)	Update(l,r,x,rc);
}
int u[Size],v[Size];		//記錄每條邊 
bool solve(int rt) {
	for(re i=head[rt]; i; i=w[i].next) {
		//遍歷所有時間包括當前區間的邊 
		int nxt=w[i].v;
		int fu=Find(u[nxt]);
		int fv=Find(v[nxt]);
		if(fu!=fv || (dist(u[nxt])^dist(v[nxt]))) {
			//當u[nxt]和v[nxt]不屬於同一個聯通塊,
			//或者屬於同一個聯通塊且加邊不會構成奇環時,這個圖仍然是一個二分圖 
			//dis[u[nxt]]^dis[v[nxt]]表示u[nxt]到v[nxt]的距離的奇偶性 
			//若這一坨爲1,表示距離爲奇數,加邊後不會形成奇環 
			//注意當u[nxt]和v[nxt]不再同一個聯通塊時,不用合併qwq 
			if(fu!=fv) {
				Union(u[nxt],v[nxt]);
			}
		} else {
			//如果加邊之後形成了奇環就返回false 
			return true;
		}
	}
	return false;
}
bool ans[Size];
void Query(int rt) {
	int pre=S.top;
	bool fail=solve(rt);
	if(fail) {
		S.pop(pre);		//回滾到之前的狀態 
		return;
	}
	if(tree[rt].l==tree[rt].r) {
		ans[tree[rt].l]=true;
	} else {
		//前向星裏存的是所有出現時間段包含當前線段樹節點的區間的邊 
		//所以此時還不用回滾 
		Query(lc);
		Query(rc);
	}
	S.pop(pre);
}
int main() {
//	freopen("1.in","r",stdin);
	n=read();
	m=read();
	T=read();
	Build(1,T,1);
	for(re i=1; i<=m; i++) {
		u[i]=read();
		v[i]=read();
		int s=read();
		int e=read();
		Update(s+1,e,i,1);
	}
	init(n);
	Query(1);
	for(re i=1; i<=n; i++) {
		if(ans[i]) {
			puts("Yes");
		} else {
			puts("No");
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章