大致題意: 給你一張圖以及每條邊的出現時間和消失時間,讓你求每個時間段這張圖是否是二分圖。
二分圖性質
二分圖有一個比較簡單的性質,即二分圖中不存在奇環。
於是題目就變成了:讓你求每個時間段這張圖是否不存在奇環。
\(LCT\)動態維護圖連通性
關於\(LCT\),詳見這篇博客:LCT入門。
接下來我們開始討論如何用\(LCT\)動態維護圖連通性。
\(LCT\)動態維護樹連通性,應該是比較簡單,因為\(LCT\)本身就是一棵樹,加邊刪邊都很容易。
而維護圖連通性最麻煩的一點,就是在於會出現環,而這樣\(LCT\)就無法按照一般的方式來加邊刪邊了。
那么,有沒有什么辦法,使得既能維護圖連通性,又不會使\(LCT\)上出現環呢?
這時就有一個比較貪心的想法。
考慮到每條邊最后都是要刪掉的,那么當構成環時,我們就可以將該環中最早要被刪掉的邊給刪去,因為這樣一來不會影響連通性,二來又可以化環為鏈,十分巧妙。
但這個方法唯一美中不足的地方就在於,它是一個離線算法,遇上強制在線就無能為力。
不過,這題可以離線做,就沒有問題了。
具體實現過程中,我們可以把邊看作節點,記錄下被刪的時間(真正的點被刪除時間可設為\(INF\))。
每個節點記錄下該子樹內被刪時間最早的節點編號,出現環時將會構成環的那條鏈摳出,然后更新即可。
維護是否不存在奇環
接下來我們要考慮如何維護奇環個數。
我們可以記錄下每個節點子樹內有多少條邊,當出現環時,判斷是否為奇環,若是則將計數器加\(1\)。同理,當刪除環時,判斷是否為奇環,若是則將計數器減\(1\)。輸出答案時判斷計數器是否為\(0\)即可。
代碼
#include<bits/stdc++.h>
#define Type template<typename I>
#define N 100000
#define M 200000
#define swap(x,y) (x^=y^=x^=y)
#define INF 1e9
using namespace std;
int n,m,t,flag,tag[N+M+5];
struct Operate
{
int x,y,pos,Begin,End;
}o1[M+5],o2[M+5];
class Class_FIO
{
private:
#define Fsize 100000
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
char ch,*A,*B,Fin[Fsize];
public:
Class_FIO() {A=B=Fin;}
Type inline void read(I& x) {x=0;while(!isdigit(ch=tc()));while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));}
template<typename I,typename... A> inline void read(I& x,A&... y) {read(x),read(y...);}
}F;
class Class_LCT//LCT動態維護圖連通性
{
private:
#define SIZE (N+M)
#define PushUp(x)\//上傳子節點信息
(\
node[x].Size=node[node[x].Son[0]].Size+node[node[x].Son[1]].Size+(x>n),node[x].Min=x,\
Val[node[x].Min]>Val[node[node[x].Son[0]].Min]&&(node[x].Min=node[node[x].Son[0]].Min),\
Val[node[x].Min]>Val[node[node[x].Son[1]].Min]&&(node[x].Min=node[node[x].Son[1]].Min)\
)
#define Rever(x) (swap(node[x].Son[0],node[x].Son[1]),node[x].Rev^=1)
#define PushDown(x) (node[x].Rev&&(Rever(node[x].Son[0]),Rever(node[x].Son[1]),node[x].Rev=0))
#define Which(x) (node[node[x].Father].Son[1]==x)
#define Connect(x,y,d) (node[node[x].Father=y].Son[d]=x)
#define IsRoot(x) (node[node[x].Father].Son[0]^x&&node[node[x].Father].Son[1]^x)
#define MakeRoot(x) (Access(x),Splay(x),Rever(x))
#define Split(x,y) (MakeRoot(x),Access(y),Splay(y))
int Stack[SIZE+5];
struct Tree
{
int Min,Size,Rev,Father,Son[2];
}node[SIZE+5];
inline void Rotate(int x)
{
register int fa=node[x].Father,pa=node[fa].Father,d=Which(x);
!IsRoot(fa)&&(node[pa].Son[Which(fa)]=x),node[x].Father=pa,Connect(node[x].Son[d^1],fa,d),Connect(fa,x,d^1),PushUp(fa),PushUp(x);
}
inline void Splay(int x)
{
register int fa=x,Top=0;
while(Stack[++Top]=fa,!IsRoot(fa)) fa=node[fa].Father;
while(Top) PushDown(Stack[Top]),--Top;
while(!IsRoot(x)) fa=node[x].Father,!IsRoot(fa)&&(Rotate(Which(x)^Which(fa)?x:fa),0),Rotate(x);
}
inline void Access(int x) {for(register int son=0;x;x=node[son=x].Father) Splay(x),node[x].Son[1]=son,PushUp(x);}
public:
int Val[SIZE+5];
Class_LCT() {Val[0]=INF;}
inline void Init(int len) {for(register int i=1;i<=len;++i) Val[i]=INF,node[i].Min=i;}//將真正的點被刪除時間設為INF
inline void Link(int x,int y) {MakeRoot(x),FindRoot(y)^x&&(node[x].Father=y);}
inline void Cut(int x,int y) {MakeRoot(x),!(FindRoot(y)^x)&&!(node[y].Father^x)&&!node[y].Son[0]&&(node[y].Father=node[x].Son[1]=0,PushUp(x));}
inline int FindRoot(int x)
{
Access(x),Splay(x);
while(node[x].Son[0]) PushDown(x),x=node[x].Son[0];
return Splay(x),x;
}
inline int QueryMin(int x,int y) {return Split(x,y),node[y].Min;}//查詢子樹內最早被刪除的邊的編號
inline int QuerySize(int x,int y) {return Split(x,y),node[y].Size;}//查詢子樹內有多少條邊
#undef SIZE
}LCT;
inline bool cmp1(Operate x,Operate y) {return x.Begin<y.Begin;}//將邊按出現時間排序
inline bool cmp2(Operate x,Operate y) {return x.End<y.End;}//將邊按消失時間排序
inline void Add(int pos)//加入一條邊
{
register int x=o1[pos].x,y=o1[pos].y,z=o1[pos].pos;
if(!(x^y)) return (void)(tag[z]=1,++flag);//如果是自環,標記該邊為形成奇環的邊,並將計數器加1
if(LCT.Val[z]=o1[pos].End,LCT.FindRoot(x)^LCT.FindRoot(y)) return LCT.Link(x,z),LCT.Link(z,y);//如果不構成環,直接連邊
register int p=LCT.QueryMin(x,y);//查詢將形成環的這條鏈中最早被刪掉的邊的編號
if(LCT.Val[z]<LCT.Val[p]) return (void)(!(LCT.QuerySize(x,y)&1)&&(tag[z]=1,++flag));//如果當前插入的這條邊先被刪去,判斷是否為奇環,然后退出函數
!(LCT.QuerySize(x,y)&1)&&(tag[p]=1,++flag),LCT.Cut(o1[p-n].x,p),LCT.Cut(p,o1[p-n].y),LCT.Link(x,z),LCT.Link(z,y);//否則,先判斷是否為奇環,再刪掉這條鏈中最早被刪掉的邊,然后插入當前邊
}
inline void Del(int pos)//刪除一條邊
{
register int x=o2[pos].x,y=o2[pos].y,z=o2[pos].pos;
if(tag[z]) return (void)(--flag);//如果這條邊在一個奇環上,則將奇環個數減1
!(LCT.FindRoot(x)^LCT.FindRoot(z))&&!(LCT.FindRoot(y)^LCT.FindRoot(z))&&(LCT.Cut(x,z),LCT.Cut(z,y),0);//如果這條邊還在圖中,則將其刪除
}
int main()
{
register int i,p1=1,p2=1;
for(F.read(n,m,t),LCT.Init(n+m),i=1;i<=m;++i) F.read(o1[i].x,o1[i].y,o1[i].Begin,o1[i].End);//讀入
for(sort(o1+1,o1+m+1,cmp1),i=1;i<=m;++i) o1[i].pos=n+i,o2[i]=o1[i];
for(sort(o2+1,o2+m+1,cmp2),i=1;i<=t;++i)
{
while(p1<=m&&o1[p1].Begin<i) Add(p1++);while(p2<=m&&o2[p2].End<i) Del(p2++);//加入和刪除邊
puts(flag?"No":"Yes");//判斷是否有奇環,輸出答案
}
return 0;
}