luogu P5787 二分图 /【模板】线段树分治
线段树分治真的难写。
二分图判定的条件是图中不存在奇环。我们可以用带权并查集来维护。现在的难点就在于对线段树\(DFS\)时回溯的时候如何删除影响。
这里可以使用可删并查集:注意到我们加入影响和删除影响的过程就是维护栈的过程,我们可以放弃并查集的路径压缩(我们按秩合并也可以保证复杂度),并在加入边时记录操作,并在删除时改回去。
- 这题的一些细节:
- 当你发现加入一条边后已经不符合二分图的限制了,则你此后(到回溯前)的所有边都可以不用加了(显然)。
- 按秩合并就是你给每个块顺便记录一个大小,合并的时候把小的往大的里加,即可保证复杂度(类似于重链剖分的复杂度分析)。
啊啊啊说白了就是烦。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int N=200009;
struct Node
{
int first,second,ok,A,B;
};
int n,m,K,f[N],fuck[N],flag,flagk,flagp,siz[N];
vector <Node> b[N*4];
void Modify(int k,int l,int r,int x,int y,int p,int q)
{
if(l>=x&&r<=y)
{
b[k].push_back((Node){p,q,0,0,0});
return;
}
int mid=l+r>>1;
if(mid>=x)
Modify(k<<1,l,mid,x,y,p,q);
if(mid<y)
Modify(k<<1|1,mid+1,r,x,y,p,q);
}
int find(int x)
{
if(x==f[x]) return x;
return find(f[x]);
}
int Get_Dis(int x)
{
if(x==f[x]) return 0;
return fuck[x]^Get_Dis(f[x]);
}
void init()
{
scanf("%d %d %d",&n,&m,&K);
for (int i=1;i<=m;i++)
{
int x,y,s,t;
scanf("%d %d %d %d",&x,&y,&s,&t);
if(s==t) continue;
Modify(1,1,K,s+1,t,x,y);
}
for (int i=1;i<=n;i++)
f[i]=i,siz[i]=1;
}
void dfs(int k,int l,int r)
{
if(!flag)
for (int i=0;i<b[k].size();i++)
{
Node v=b[k][i];
int x=v.first,y=v.second;
int A=find(x),B=find(y);
if(siz[A]<siz[B])
swap(x,y),swap(A,B);
if(A!=B)
{
siz[A]+=siz[B];
f[B]=A,fuck[B]=Get_Dis(y)^Get_Dis(x)^1;
b[k][i].ok=1,b[k][i].A=A,b[k][i].B=B;
}
else if(!(Get_Dis(x)^Get_Dis(y)))
{
flag=1;
flagk=k;
flagp=i;
break;
}
}
if(l==r)
puts(flag?"No":"Yes");
else
{
int mid=l+r>>1;
dfs(k<<1,l,mid);
dfs(k<<1|1,mid+1,r);
}
if(!flag)
{
for (int i=b[k].size()-1;i>=0;i--)
{
Node v=b[k][i];
if(!v.ok)
continue;
f[v.B]=v.B,fuck[v.B]=0,siz[v.A]-=siz[v.B];
}
}
else if(k==flagk)
{
flag=0;
if(flagp==0) return;
for (int i=flagp-1;i>=0;i--)
{
Node v=b[k][i];
if(!v.ok)
continue;
f[v.B]=v.B,fuck[v.B]=0,siz[v.A]-=siz[v.B];
}
}
}
int main()
{
init();
dfs(1,1,K);
return 0;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!