题目链接:传送门
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)
(u,v)时分情况讨论:
如果
u
u
u和
v
v
v本来不在同一个联通块中,则连边也不珂能形成环,所以直接合并就好qwq
合并时是代表元合并,假设
x
x
x到代表元的距离的奇偶性为
d
i
s
[
x
]
dis[x]
dis[x],
0
0
0表示偶数,
1
1
1表示奇数。
代表元之间的距离应为
d
i
s
[
u
]
dis[u]
dis[u] ^
d
i
s
[
v
]
dis[v]
dis[v] ^
1
1
1(^表示异或),表示
u
u
u到代表元的距离加上
v
v
v到代表元的距离加上1的奇偶性。
如果
u
u
u和
v
v
v在同一个联通块中,且
u
u
u和
v
v
v到代表元距离之和为偶数,则连边之后会形成奇环(加上了新的边,偶数变奇数)
解析
如果边出现的时间段不相交,发现每次消失的边都是最后加入的qwq。
那么珂以用资磁回滚的并查集,搞一个栈,像回滚莫队一样回滚。
但是这里边出现的时间段珂以相♂交,所以考虑把所有线段分成若干不相♂交的线段。
线段?线段树!
首先把每个时间段对应到线段树上的若干区间。
举个栗子,假设
T
=
4
T=4
T=4,即所有线段都在
[
1
,
4
]
[1,4]
[1,4]内,然后有一条边出现的时间是
[
1
,
3
]
[1,3]
[1,3]。
脑补一下,珂以发现这里会把它映射到
[
1
,
2
]
[1,2]
[1,2]和
[
3
,
3
]
[3,3]
[3,3]这两个区间上qwq。
意思就是让它在
1
1
1时刻出现,在
2
2
2时刻末消失,再在
3
3
3时刻出现,
3
3
3时刻末消失。
这里用前向星存下线段树上每个节点被哪些边包含(用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;
}