題目鏈接:傳送門
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
先不考慮邊消失的情況,考慮怎樣判二分圖:想到用帶權並查集,帶權並查集上維護點到代表元的距離的奇偶性。
加入一條邊時分情況討論:
如果和本來不在同一個聯通塊中,則連邊也不珂能形成環,所以直接合並就好qwq
合併時是代表元合併,假設到代表元的距離的奇偶性爲,表示偶數,表示奇數。
代表元之間的距離應爲 ^ ^ (^表示異或),表示到代表元的距離加上到代表元的距離加上1的奇偶性。
如果和在同一個聯通塊中,且和到代表元距離之和爲偶數,則連邊之後會形成奇環(加上了新的邊,偶數變奇數)
解析
如果邊出現的時間段不相交,發現每次消失的邊都是最後加入的qwq。
那麼珂以用資磁回滾的並查集,搞一個棧,像回滾莫隊一樣回滾。
但是這裏邊出現的時間段珂以相♂交,所以考慮把所有線段分成若干不相♂交的線段。
線段?線段樹!
首先把每個時間段對應到線段樹上的若干區間。
舉個栗子,假設,即所有線段都在內,然後有一條邊出現的時間是。
腦補一下,珂以發現這裏會把它映射到和這兩個區間上qwq。
意思就是讓它在時刻出現,在時刻末消失,再在時刻出現,時刻末消失。
這裏用前向星存下線段樹上每個節點被哪些邊包含(用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;
}