【洛谷 P5787】二分图 线段树分治【二分图】【线段树分治】

28 篇文章 0 订阅
17 篇文章 0 订阅

在这里插入图片描述
在这里插入图片描述


解题思路

题目大意:一张图有 n 个节点的图, 在 k 时间中会出现 m 条边,表示有一条连接 x,y 的边在 l 时刻出现 r 时刻消失,求问在第 i 个时间段中图是否为二分图。

先对于二分图来分析

如果顶点 V 可分割为两个互不相交的子集 (A,B) ,并且图中的每条边 (i,j)所关联的两个顶点 i 和 j 分别属于这两个不同的顶点集 (i∈A,j∈B) ,则称图 G 为一个二分图。 —— 百度百科。

  • 在一般的做法中对于一个图是否为二分图,我们一般是采用染色法,如果一个图为二分图,那么一条边所连接的两个点一定是在不同集合的,也就是点的颜色不同。

这个方法时间复杂度和空间复杂度都必须优化。
我们知道图是二分图的充要条件是不存在奇环,这个可以用到一个叫扩展域并查集的东西维护

  • 扩展域并查集:对于一个节点 i i i,我们将其拆分为两个节点 ( i , i + n ) (i,i+n) ii+n。一个属于集合 S ,另一个属于集合 T 。把 i i i j j j相连,就把 ( i , j + n ) , ( j , i + n ) (i,j+n),(j,i+n) ij+nji+n连在一起。因为一条边所连接的两个节点就必须在不同的集合中,所以,当本应该在 S S S 中的 i i i点和在 T 中 i + n i+n i+n点在一个集合中了,那么这张图就不是二分图。
    例子:假设 i i i j j j相连,则把 ( i , j + n ) , ( j , i + n ) (i,j+n),(j,i+n) ij+nji+n连在一起(使用并查集让它们属于一个集合),再让k和j相连,则把 ( k , j + n ) , ( j , k + n ) (k,j+n),(j,k+n) kj+njk+n连在一起。现在 ( i , j + n , k ) (i,j+n,k) ij+nk属于一个集合, ( i + n , j , k + n ) (i+n,j,k+n) i+njk+n属于一个集合。
    如果现在连接 i , k i,k ik,即把 ( i , k + n ) , ( k , i + n ) (i,k+n),(k,i+n) ik+nki+n连在一起就会让(i,j+n,k,k+n)属于一个集合, ( i + n , j , k + n , k ) (i+n,j,k+n,k) i+njk+nk属于一个集合。发现本应该在 S S S 中的 k k k点和在 T 中 k + n k+n k+n点在一个集合中了,那么这张图就不是二分图。

对于时间段分析
我们把时间轴画出来,那么对于每一条边它总是在时间轴上覆盖了一些区域。所以对于一条边我们可以将其分解。

我们先将时间轴构建 logk 层,然后就像插入区间一样,把每一条边插入。因为对于线段树上的一个节点,它的子节点也一定被这条边覆盖,那么我们只需要在父亲节点储存这条边。那么这样对于一条边最大也只会分解为 logk 不重复的较小的边。

在这里插入图片描述
这样我们把所有边在线段树上映射好,最后一次性处理线段树的所有叶子结点(因为区间总长k,要输出对应k个时间段)。但是我们发现当我们处理完一个节点的子节点后,我们必须将这个节点对应的时间新增的边删掉,将并查集还原到处理之前的状态才可以递归处理其他节点。

删边处理方法:我们可以把所有操作放在栈里,当我们退出时,撤销原操作就行了。
但是这样我们就不可以路径压缩了,因为每个节点是要储存他真正的父亲的。为了保证复杂度的正确性,必须要按秩合并。将树的深度大的合并到深度小的的父亲下面,这样树的高度是不会高于 O ( log ⁡ n ) O(\log n) O(logn) 的。

总结:建一个时间轴上的线段树,再通过并查集维护答案。


代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<set>
#define ll long long
#define ldb long double
using namespace std;

int n,m,k,x,y,l,r,top;
int fa[400010],h[400010];

struct c {
	int x,y;
} a[400010];

struct Stack {
	int x,y,v;
} st[400010];

vector<int>t[1000100];

void up(int dep,int l,int r,int x,int y,int v) {
//	if(l>y||r<x)return;
	if(x<=l&&y>=r) {
		t[dep].push_back(v);
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid)up(dep*2,l,mid,x,y,v);
	if(y>mid)up(dep*2+1,mid+1,r,x,y,v);
}

int find(int x) {
	if(fa[x]==x)return fa[x];
	return find(fa[x]);
}

void add(int x,int y) {
	int fx=find(x),fy=find(y);
	if(h[fx]>h[fy])swap(fx,fy);
	st[++top]=(Stack) {
		fx,fy,h[fx]==h[fy]
	};
	fa[fx]=fy;
	if(h[fx]==h[fy])
		h[fy]++;
}

void work(int dep,int l,int r) {
	int lasttop=top,ok=0;
	for(int i=0; i<t[dep].size(); i++) {
		int aa=find(a[t[dep].at(i)].x);
		int bb=find(a[t[dep].at(i)].y);
		if(aa==bb) {
			for(int j=l; j<=r; j++)
				printf("No\n");
			ok=1;
			break;
		}
		add(a[t[dep].at(i)].x,a[t[dep].at(i)].y+n);
		add(a[t[dep].at(i)].x+n,a[t[dep].at(i)].y);
	}
	if(!ok) {
		if(l==r)printf("Yes\n");
		else {
			int mid=(l+r)>>1;
			work(dep*2,l,mid);
			work(dep*2+1,mid+1,r);
		}
	}
	while(top>lasttop) {
		h[fa[st[top].x]]-=st[top].v;
		fa[st[top].x]=st[top].x;
		top--;
	}
	return;
}

int main() {
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1; i<=m; i++) {
		scanf("%d%d%d%d",&a[i].x,&a[i].y,&l,&r);
		up(1,1,k,l+1,r,i);
	}
	for(int i=1; i<=2*n; i++)
		fa[i]=i,h[i]=1;
	work(1,1,k);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值