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
1 2 0 2
2 3 0 3
1 3 1 2
Sample Output
Yes
No
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。
题解Here!
又是一道神题,好难。。。
开始的时候并不知道$LCT$怎么判二分图。。。
然后翻题解。。。百度一下二分图。
二分图就是在任意时刻,这个图中一定没有奇环。
奇环是什么?就是有奇数个点的环啊。。。
其实就是判每一个时刻有没有奇环。
然后我们考虑如何维护。
我们先随便弄出一棵生成树,然后我们想维护这样的集合$S$:$S$中的任意一条边都会和生成树构成奇环。
有一种情况我们没有办法处理,就是:把集合的边加上,然后,树边没了。。。
我们还要把集合中的边换成树边,非常麻烦,码量贼大。。。
所以我们维护以删除时间为边权的最大生成树,就可以解决以上这种情况了。
然后就是$LCT$板子了,开始码码码。。。
然后我一下午就这么搭进去了。。。
据说还有分治+并查集的算法?反正本蒟蒻不会。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 400010 #define MAX 2147483646 using namespace std; int n,m,q,ans=0; int num[MAXN],val[MAXN]; struct Edge{ int u,v,start,end; friend bool operator <(const Edge &p,const Edge &q){ if(p.start==q.start)return p.end<q.end; return p.start<q.start; } }edge[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } namespace LCT{ int top=0,stack[MAXN]; struct Link_Cut_Tree{ int f,flag,son[2]; int v,s; }a[MAXN]; inline bool isroot(int rt){ return a[a[rt].f].son[0]!=rt&&a[a[rt].f].son[1]!=rt; } inline void pushup(int rt){ if(!rt)return; a[rt].s=a[a[rt].son[0]].s+a[a[rt].son[1]].s+1; a[rt].v=rt; if(val[a[a[rt].son[0]].v]<val[a[rt].v])a[rt].v=a[a[rt].son[0]].v; if(val[a[a[rt].son[1]].v]<val[a[rt].v])a[rt].v=a[a[rt].son[1]].v; } inline void pushdown(int rt){ if(!rt||!a[rt].flag)return; a[a[rt].son[0]].flag^=1;a[a[rt].son[1]].flag^=1;a[rt].flag^=1; swap(a[rt].son[0],a[rt].son[1]); } inline void turn(int rt){ int x=a[rt].f,y=a[x].f,k=a[x].son[0]==rt?1:0; if(!isroot(x)){ if(a[y].son[0]==x)a[y].son[0]=rt; else a[y].son[1]=rt; } a[rt].f=y;a[x].f=rt;a[a[rt].son[k]].f=x; a[x].son[k^1]=a[rt].son[k];a[rt].son[k]=x; pushup(x);pushup(rt); } void splay(int rt){ top=0; stack[++top]=rt; for(int i=rt;!isroot(i);i=a[i].f)stack[++top]=a[i].f; while(top)pushdown(stack[top--]); while(!isroot(rt)){ int x=a[rt].f,y=a[x].f; if(!isroot(x)){ if((a[y].son[0]==x)^(a[x].son[0]==rt))turn(rt); else turn(x); } turn(rt); } } void access(int rt){ for(int i=0;rt;i=rt,rt=a[rt].f){ splay(rt); a[rt].son[1]=i; pushup(rt); } } inline void makeroot(int rt){access(rt);splay(rt);a[rt].flag^=1;} int findroot(int rt){ access(rt);splay(rt); while(a[rt].son[0])rt=a[rt].son[0]; return rt; } inline void split(int x,int y){makeroot(x);access(y);splay(y);} inline void link(int x,int y){makeroot(x);a[x].f=y;} inline void cut(int x,int y){split(x,y);a[x].f=a[y].son[0]=0;} void add_edge(int now,int now_time){ int u=edge[now].u,v=edge[now].v; if(u==v&&edge[now].end>now_time){ ans++; num[edge[now].end]++; return; } if(findroot(u)!=findroot(v)){link(now+n,u);link(v,now+n);} else{ int x; bool flag=false; split(u,v); if(!((a[v].s>>1)&1))flag=true; if(val[a[v].v]>=edge[now].end)x=now; else{ x=a[v].v-n; cut(x+n,edge[x].u);cut(x+n,edge[x].v); link(now+n,u);link(v,now+n); } if(flag&&edge[x].end>now_time){ ans++; num[edge[x].end]++; } } } } void work(){ int now=1; for(int i=1;i<=q;i++){ while(now<=m&&edge[now].start<=i){ LCT::add_edge(now,i); now++; } ans-=num[i]; if(ans)printf("No "); else printf("Yes "); } } void init(){ n=read();m=read();q=read(); val[0]=MAX; for(int i=1;i<=n;i++){ val[i]=MAX; LCT::a[i].s=1; LCT::a[i].v=i; } for(int i=1;i<=m;i++){ edge[i].u=read();edge[i].v=read(); edge[i].start=read()+1;edge[i].end=read()+1; } sort(edge+1,edge+m+1); for(int i=1;i<=m;i++){ val[i+n]=edge[i].end; LCT::a[i+n].s=1; LCT::a[i+n].v=i+n; } } int main(){ init(); work(); return 0; }