Luogu P5787 二分图 /【模板】线段树分治 【模板题】

题目

在这里插入图片描述

思路

考虑这样一个问题:

  • 有一些操作,每个操作只在 l ∼ r l\sim r lr 的时间段内有效。
  • 有一些询问,每个询问某一个时间点所有操作的贡献。

对于这样的询问,我们可以离线后在时间轴上建一棵线段树,这样对于每个操作,相当于在线段树上进行区间操作。
遍历整颗线段树,到达每个节点时执行相应的操作,然后继续向下递归,到达叶子节点时统计贡献,回溯时撤销操作即可。
这样的思想被称为线段树分治,可以在低时间复杂度内解决一类在线算法并不优秀的问题。


对于此题
首先,图是二分图的充要条件是不存在奇环,这个可以用扩展域并查集轻松维护。
按照上述思想建一棵线段树,对于每条边,将它按照线段树区间操作的方式划分成 O ( log ⁡ k ) \mathcal O(\log k) O(logk) 段,用 vector 挂在线段树的节点上。
遍历时,从根节点出发,每到一个节点,将挂在该节点上的所有边合并,然后递归处理左儿子和右儿子。如果发现有某条边合并会出现奇环,那么当前线段树节点所对应的时间区间都不会形成二分图。
当到达叶子节点时,如果合并了所有挂在当前节点上的边,依旧满足二分图的性质,那么可以直接输出 Yes。
回溯时,由于并查集不支持删边,我们可以使用可撤销并查集,即用一个栈记录下所有对并查集的操作。由于可撤销,因此不能路径压缩,为保证复杂度,必须按秩合并

代码

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n,m,k,x,y,l,r,fa[2000010],h[2000010],top;
vector<int>q[2000010];
struct stu
{
	int x,y,add;
}st[2000010];
struct stu3
{
	int x,y;
}e[2000010];
int find(int faa)
{
	while(fa[faa]!=faa)
	  faa=fa[faa];
	return fa[faa];
}
void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(h[fx]>h[fy])
	  swap(fx,fy);
	st[++top]=(stu){fx,fy,h[fx]==h[fy]};
	fa[fx]=fy;
	if(h[fx]==h[fy])
	  h[fy]++;
}
void solve(int k,int l,int r)
{
	int bj=1,ltop=top;
	for(int i=0; i<q[k].size(); i++)
	 {
	 	int fx=find(e[q[k].at(i)].x),fy=find(e[q[k].at(i)].y);
	 	if(fx==fy)
	 	 {
	 	 	for(int j=l; j<=r; j++)
	 	 	   printf("No\n");
	 	 	bj=0;
	 	 	break;
		 }
	    merge(e[q[k].at(i)].x,e[q[k].at(i)].y+n);
	    merge(e[q[k].at(i)].y,e[q[k].at(i)].x+n);
	 }
	if(bj)
	 {
	 	int mid=(l+r)/2;
	 	if(l==r)
	 	  printf("Yes\n");
	 	else
	 	 {
	 	 	solve(k*2,l,mid);
	 	 	solve(k*2+1,mid+1,r);
		 }
	 }
	while(top>ltop)
	 {
	 	h[fa[st[top].x]]-=st[top].add;
	 	fa[st[top].x]=st[top].x;
	 	top--;
	 }
	return;
}
void insert(int k,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	 {
	 	q[k].push_back(d);
	 	return;
	 }
	int mid=(l+r)/2;
	if(x<=mid)
	  insert(k*2,l,mid,x,y,d);
	if(y>mid)
	  insert(k*2+1,mid+1,r,x,y,d);
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1; i<=m; i++)
	 {
	 	scanf("%d%d%d%d",&e[i].x,&e[i].y,&l,&r);
	 	l++,insert(1,1,k,l,r,i);
	 }
	for(int i=1; i<=2*n; i++)
	   fa[i]=i,h[i]=1;
	solve(1,1,k);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值