线段树分治(加可撤销并查集)学习笔记


前言

之前没认真学习留下的锅,之后都得补\(\Theta \omega \Theta\)

又是一个之前用过但是没好好学,今天又碰上了结果不会的知识点,于是赶紧回来补锅。

大致内容

在某些时候中我们会遇到形似“xxx在xxx时间点出现在xxx时间点消失,问若干个时间点的状态”这样的问题,这个时候就可以用线段树分治解决。

非常显然地我们可以对时间线开一棵线段树,将所有操作离线之后把它们拍到这棵线段树上,之后从上往下对这棵线段树进行\(DFS\),每遇到一个区间就将其中的信息贡献到答案中,等到递归到\(l==r\)时即可得到一个时间点的信息。一个区间结束之后再将它的贡献消除以防止对后面区间的影响即可。

伪代码:

void 添加操作(节点编号,节点控制的区间,添加区间,想要添加的信息){
	if(添加区间覆盖了节点控制的区间) return 在节点中加上信息;
	添加操作(左儿子),添加操作(右儿子);
}

void dfs(节点编号,节点控制的区间,当前答案){
      当前节点的信息产生贡献;
      计算新答案;
      if(l==r)输出答案;
      else dfs(左儿子,左儿子区间,新答案),dfs(右儿子,右儿子区间,新答案);
      消除贡献;
}

这个问题最经典的一个版本为给你一张图,图上的边在某个时间点出现,又在某个时间点消失,询问你每个时间点上这张图有几个联通块/是不是二分图……

对于这一类问题我们显然可以对线段树上的每一个节点开一个\(vector\),将所有在这个区间内出现的边全都拍进去,在向下\(DFS\)的时候维护一个可撤销并查集,遇到区间就将其中的边全都加到并查集里面,回溯的时候再全都撤销掉。

由于蒟蒻\(DarthVictor\)之前连可撤销并查集都没怎么用过所以顺带提一下可撤销并查集。

并查集每次合并的时候对撤销最大的影响是其中一个联通块的父亲/大小等信息会消失,因此可撤销并查集即在每次进行合并的时候都把联通块的信息保存在一个栈里,之后撤销的时候再从中提取信息并进行还原。当然,这意味着我们不可能再使用路径压缩进行优化了,因为这同样会造成无法记录的数据删除信息丢失,所以我们只能用按秩合并进行优化。

例题

洛谷P5758 二分图 /【模板】线段树分治

解说

非常板子的一道题,就像上文说的那样搞一搞就行了。唯一要提一下的是如何每添加一条边都快速地计算出当前图是否为二分图……使用拓展域并查集即可。

具体一些实现见代码。

代码

#include
#define re register
#define mid (l+r>>1)
#define lch (rt<<1)
#define rch (rt<<1|1)
using namespace std;
const int lzw=1e5+3;
int fa[lzw<<1],n,m,k,size[lzw<<1];
struct edge{
	int a,b;
};
vector tree[lzw<<2];
struct inf{
	int id,fa,size;
};
stack stk;
void add(int rt,int l,int r,int s,int t,int a,int b){//添加操作
	if(s<=l&&t>=r) return tree[rt].push_back((edge){a,b}),void();
	if(s>r||tsize[yy]) swap(x,yy);
				stk.push((inf){x,fa[x],size[x]});
				stk.push((inf){yy,fa[yy],size[yy]});
				fa[x]=yy,size[yy]+=size[x];
			}
			if(xx!=y){
				if(size[xx]>size[y]) swap(xx,y);
				stk.push((inf){xx,fa[xx],size[xx]});
				stk.push((inf){y,fa[y],size[y]});
				fa[xx]=y,size[y]+=size[xx];
			}
			if(find(tree[rt][i].a)==find(tree[rt][i].a+n)) ava=1;
			if(find(tree[rt][i].b)==find(tree[rt][i].b+n)) ava=1;
		}
	}
	ans|=ava;
	if(l==r) puts(ans?"No":"Yes");
	else dfs(lch,l,mid,ans),dfs(rch,mid+1,r,ans);
	while(stk.size()!=now) fa[stk.top().id]=stk.top().fa,size[stk.top().id]=stk.top().size,stk.pop();//回溯
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(re int i=1;i<=m;i++){
		int x,y,l,r;
		scanf("%d%d%d%d",&x,&y,&l,&r);
		add(1,1,k,l+1,r,x,y);
	}
	for(re int i=1;i<=(n<<1);i++) fa[i]=i,size[i]=1;
	dfs(1,1,k,0);
	return 0;
}

幸甚至哉,歌以咏志。

相关