题解 P5787 二分图 /【模板】线段树分治
分析
线段树分治呢,处理的就是这些基于时间上的物品加入删除问题,我们将所有事件离线处理,每一个询问代表一个时刻,对于每一个物品,处理它所存在的时段,概括一下就是用线段树来处理每一个物品能对哪些询问起作用。
所以线段树每一个节点就代表了一个时段,而在询问中我们通常是找某一个时间点,所以类似于标记永久化的思想,在从上到下的过程中,如果能覆盖一个区间,那区间内的点也能同样造成影响,所以我们先将每一个物品加到它对应的区间,用 vector 存储完全覆盖某一区间的物品有哪些。
然后我们就可以从大区间向小区间找,每到一个区间时,将完全覆盖该区间的边所带来的影响处理,然后将处理后的结果带给更小的区间。
现在知道了线段树分治了,考虑本题每加入一条边的影响。
是否形成二分图,就需要将点分为两类,使得每一条边的两侧都是不同类的点,我们再引入种类并查集。
就是将并查集扩大几倍,分为几部分,每部分表示每一个个体可能属于的种群,相当于每个个体在每一部分都有自己的点,每一部分内部的连边就相当于同类关系,不同部分间的连边就代表对应题目中所给出的种群间的某种特殊联系。
例如此题,我们将每个点拆为两个点,\(i\) 表示第一类,\(i+n\) 表示第二类,\(i\) 与 \(j+n\) 同一并查集内表示它们不同类,\(i\) 与 \(j\) 或 \(i+n\) 与 \(j+n\) 在同一并查集内表示它们同类,所以在连边时先判断两端点是否同类,如果同类则说明当前分治到的整个区间都不可行,因为下面只会连更多的边,不会抵消这个冲突,只可能带来更多的冲突,就可以直接回退。否则就将代表两者不同类的边连上。
分治中还存在的一个问题就是递归完左区间后,需要去除左区间对右区间的影响,所以此题的并查集不能路径压缩,因为在回到上一层时需将在该区间连上的边断掉,所以在连边时记录本次连边的儿子,以及父亲原并查集的深度(因为要保证按秩合并的正确性),在返回时倒序将状态还原即可。
代码
#include
using namespace std;
#define ls rt<<1
#define rs rt<<1|1
inline void read(int &res){
res=0;
int f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')res=(res<<1)+(res<<3)+c-48,c=getchar();
res*=f;
}
int n,m,k;
int l[200005],r[200005],u[200005],v[200005];
int flag[200005][2];
int flagg[200005][2];
vectorV[400005];
void modify(int rt,int l,int r,int L,int R,int x){
if(L>R)return;
if(l>=L&&R>=r){
V[rt].push_back(x);
return;
}
int mid=(l+r)>>1;
if(mid>=L)modify(ls,l,mid,L,R,x);
if(mid=0;j--){
del(V[rt][j]);//倒序删除
}
return;
}
}
if(l==r){
for(int i=V[rt].size()-1;i>=0;i--){
del(V[rt][i]);
}
puts("Yes");
return;
}
int mid=(l+r)>>1;
sol(ls,l,mid);
sol(rs,mid+1,r);
for(int i=V[rt].size()-1;i>=0;i--){
del(V[rt][i]);
}
}
int main()
{
read(n);read(m);read(k);
for(int i=1;i<=m;i++){
read(u[i]);read(v[i]);read(l[i]);read(r[i]);
modify(1,1,k,l[i]+1,r[i],i);
}
for(int i=1;i<=2*n;i++)fa[i]=i,siz[i]=1;//预处理
sol(1,1,k);
}