题目
题目链接:https://www.luogu.com.cn/problem/P5787
神犇有一个 \(n\) 个节点的图。
因为神犇是神犇,所以在 \(k\) 时间内有 \(m\) 条边会出现后消失。
神犇要求出每一时间段内这个图是否是二分图。
这么简单的问题神犇当然会做了,于是他想考考你。
\(n,k=10^5,m=2\times 10^5\)。
思路
建立一棵线段树,把每一个操作的区间 \([l,r]\) 在线段树上找到,并在每一个区间的 vector 中加入连接 \(x,y\) 两点的操作。
然后深度优先搜索整棵线段树,搜索到一个区间 \([l,r]\) 时,把这个区间的所有操作执行,具体的,为了判断是不是二分图,我们可以采用扩展域并查集,把每一个点拆成两个点,然后类比黑白染色,黑点连白点,白点连黑点。如果某个时刻存在一个点的黑点和白点在同一集合内,说明存在奇环,\([l,r]\) 均不是二分图。
否则继续分治,如果到达叶子时依然没有奇环,那么就是二分图。
回溯的时候需要撤销并查集,考虑采用不路径压缩,只按秩合并的并查集即可。
估计只有我一个人按秩合并一直以为是按照大小吧。其实是按照深度。
时间复杂度 \(O(k\log k\log m)\)。
代码
#include <bits/stdc++.h>
#define mp make_pair
#define ST first
#define ND second
using namespace std;
const int N=200010;
int n,m,t,father[N*2],dep[N*2];
stack<int> st;
int find(int x)
{
return x==father[x]?x:find(father[x]);
}
bool merge(int x,int y)
{
x=find(x); y=find(y);
if (x==y) return 1;
if (dep[x]<dep[y]) swap(x,y);
father[y]=x; dep[x]+=(dep[x]==dep[y]);
st.push(y);
if (find(x)==find(x+n) || find(y)==find(y+n)) return 0;
return 1;
}
struct SegTree
{
vector<pair<int,int> > e[N*4];
void update(int x,int l,int r,int ql,int qr,int p,int q)
{
if (ql<=l && qr>=r)
{
e[x].push_back(mp(p,q));
return;
}
int mid=(l+r)>>1;
if (ql<=mid) update(x*2,l,mid,ql,qr,p,q);
if (qr>mid) update(x*2+1,mid+1,r,ql,qr,p,q);
}
void solve(int x,int l,int r)
{
int cnt=st.size(); bool flag=1;
for (int i=0;i<e[x].size();i++)
{
int p=e[x][i].ST,q=e[x][i].ND;
if (!merge(p,q+n)) { flag=0; break; }
if (!merge(p+n,q)) { flag=0; break; }
}
if (!flag)
for (int i=l;i<=r;i++) printf("No\n");
else if (l<r)
{
int mid=(l+r)>>1;
solve(x*2,l,mid); solve(x*2+1,mid+1,r);
}
else printf("Yes\n");
for (;st.size()>cnt;st.pop())
{
int x=st.top();
dep[father[x]]-=(dep[father[x]]==dep[x]); father[x]=x;
}
}
}seg;
int main()
{
scanf("%d%d%d",&n,&m,&t);
for (int i=1,x,y,l,r;i<=m;i++)
{
scanf("%d%d%d%d",&x,&y,&l,&r);
seg.update(1,1,t,l+1,r,x,y);
}
for (int i=1;i<=n*2;i++)
father[i]=i;
seg.solve(1,1,n);
return 0;
}