线段树分治-学习笔记

线段树分治-学习笔记

阅前须知:本文给出了线段树分治的一道例题以及多道习题,同时给出了部分实现的代码,帮助学习线段树分治。

总述

线段树分治是一种离线算法,在于把修改挂在线段树的节点上,通过遍历线段树求出每个叶子节点的答案,以减小复杂度。

例题 P5787 二分图

题目大意\(n\) 个点的图上,有 \(m\) 条边,第 \(i\) 条边 \((x,y)\) 在时刻 \(l_i\sim r_i\) 存在,问每个时刻的图是否是二分图。

解题思路:首先如果是动态加边,没有删边,怎么判断一个图是否是二分图?

可以考虑种类并查集,一共两类,一条边连接不同种类,如果连边时发现两个点属于同一种类,那么就不是二分图。这样我们就能做到 \(O(mk\alpha(n))\)

考虑分治思想,我们把不同的询问取出它们的共同的修改,这样就能减小复杂度。

考虑把时刻 \(l_i\sim r_i\) 的修改挂在线段树上的 \(\log k\) 个区间上,这样一共有 \(m\log k\) 个修改,是可接受的。接下来我们从树根开始走,走到一个点时把修改加上,离开这个子树时把修改撤回,走到叶子时就可以记录答案了。

我们可以使用启发式合并的并查集,这样在 getfa 时就不用路径压缩,然后合并时用 vector 记录 fasz 的每个修改的位置与原来的值,在回溯时把这些修改撤销即可。

时间复杂度 \(O(k\log k\log n)\)

参考实现:

int fa[N],sz[N];
int getfa(int x) {
	if(fa[x]==x) return x;
	return getfa(fa[x]);
}
#define mid ((l+r)>>1)
#define ls (x<<1)
#define rs (ls|1)
vp up[N];
void add(int x,int l,int r,int L,int R,int a,int b) {
	if(R<l||r<L)return;
	if(L<=l&&r<=R) {
		up[x].pb({a,b});
		return;
	}
	add(ls,l,mid,L,R,a,b),add(rs,mid+1,r,L,R,a,b);
}
void solve(int x,int l,int r,int flag) {
	vp o1,o2;
	if(!flag)for(auto [a,b]:up[x]) {
		int u=getfa(a),v=getfa(b);
		if(u==v) flag=1;
		u=getfa(a),v=getfa(b+n);
		if(u!=v) {
			if(sz[u]<sz[v]) swap(u,v);
			o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
			sz[u]+=sz[v],fa[v]=u;
		}
		u=getfa(a+n),v=getfa(b);
		if(u!=v) {
			if(sz[u]<sz[v]) swap(u,v);
			o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
			sz[u]+=sz[v],fa[v]=u;
		}
	}
	if(l==r) {
		if(flag) write("No\n");
		else write("Yes\n");
		for(auto [a,b]:o1) sz[a]=b;
		for(auto [a,b]:o2) fa[a]=b;
		return;
	}
	solve(ls,l,mid,flag);
	solve(rs,mid+1,r,flag);
	reverse(o1.begin(),o1.end());
	reverse(o2.begin(),o2.end());
	for(auto [a,b]:o1) sz[a]=b;
	for(auto [a,b]:o2) fa[a]=b;
}
signed main(){
	read(n,m,K);
	fo(i,1,n*2) fa[i]=i,sz[i]=1;
	fo(i,1,m) {
		int l,r,x,y; 
		read(x,y,l,r);
		++l,++r;
		if(l==r) continue; 
		add(1,1,K,l,r-1,x,y);
	}
	solve(1,1,K,0);
	return 0;
}

练习A AHOI2013 连通图

题目大意:每次询问断掉若干条边,问图是否还连通。

解题思路:我们把每条边断开的位置找出来,这样就分成了若干个区间,每个区间都表示这条边连上。

考虑线段树分治,把区间挂在线段树上。那么考虑怎么判断是否连通,用并查集缩点,如果成功缩点连通块个数减一,判断连通块个数是否为 1 即可。

参考实现:

const int N=1e6+5;
int X[N],Y[N];
vi d[N];
int fa[N],sz[N];
int getfa(int x) {
	if(fa[x]==x)return x;
	return getfa(fa[x]);
}
int n,m,K;
#define ls (x<<1)
#define rs (ls|1)
#define mid ((l+r)>>1)
vp c[N];
void add(int x,int l,int r,int L,int R,int a,int b) {
	if(R<l||r<L) return;
	if(L<=l&&r<=R) {
		c[x].pb({a,b});
		return;
	} 
	add(ls,l,mid,L,R,a,b),add(rs,mid+1,r,L,R,a,b);
}
void solve(int x,int l,int r,int s) {
	vp o1,o2;
	for(auto [a,b]:c[x]) {
		a=getfa(a),b=getfa(b);
		if(a!=b) {
			if(sz[a]<sz[b]) swap(a,b);
			o1.pb({a,sz[a]}),o2.pb({b,fa[b]});
			fa[b]=a,sz[a]+=sz[b];
			--s;
		}
	}
	if(l==r) {
		if(s==1) write("Connected\n");
		else write("Disconnected\n");
		reverse(o1.begin(),o1.end());
		reverse(o2.begin(),o2.end());
		for(auto [a,b]:o1) sz[a]=b;
		for(auto [a,b]:o2) fa[a]=b;
		return;
	}
	solve(ls,l,mid,s);
	solve(rs,mid+1,r,s);
	reverse(o1.begin(),o1.end());
	reverse(o2.begin(),o2.end());
	for(auto [a,b]:o1) sz[a]=b;
	for(auto [a,b]:o2) fa[a]=b;
}
signed main(){
	read(n,m);
	fo(i,1,m) read(X[i],Y[i]);
	read(K);
	fo(i,1,K) {
		int t; read(t);
		fo(j,1,t) {
			int x; read(x);
			d[x].pb(i);
		}
	}
	fo(i,1,m) {
		int lst=0;
		for(auto j:d[i]) {
			if(lst+1<j) add(1,1,K,lst+1,j-1,X[i],Y[i]);
			lst=j;
		}
		if(lst<K) add(1,1,K,lst+1,K,X[i],Y[i]);
	}
	fo(i,1,n) sz[i]=1,fa[i]=i;
	solve(1,1,K,n);
	return 0;
}

练习B CF813F

记录每一种边上一次出现的位置即可转化为例题。

参考实现:

const int N=1e6+5;
int fa[N],sz[N];
int getfa(int x) {
	if(fa[x]==x) return x;
	return getfa(fa[x]);
}
int tot;
map<pi,int> mp;
int X[N],Y[N];
int lst[N],opt[N];
vp d[N];
#define ls (x<<1)
#define rs (ls|1)
#define mid ((l+r)>>1)
void add(int x,int l,int r,int L,int R,int a,int b) {
	if(L<=l&&r<=R) {
		d[x].pb({a,b});
		return;
	}
	if(R<l||r<L) return;
	add(ls,l,mid,L,R,a,b),add(rs,mid+1,r,L,R,a,b);
}
void solve(int x,int l,int r,int flag){
	vp o1,o2;
	for(auto [a,b]:d[x]) {
		int u=getfa(a),v=getfa(b);
		if(u==v) flag=1;
		v=getfa(b+n);
		if(u!=v) {
			if(sz[u]<sz[v]) swap(u,v);
			o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
			sz[u]+=sz[v],fa[v]=u;
		}
		u=getfa(a+n),v=getfa(b);
		if(u!=v) {
			if(sz[u]<sz[v]) swap(u,v);
			o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
			sz[u]+=sz[v],fa[v]=u;
		}
	}
	if(l==r) {
		if(flag) write("NO\n");
		else write("YES\n");
		reverse(o1.begin(),o1.end());
		reverse(o2.begin(),o2.end());
		for(auto [a,b]:o1) sz[a]=b;
		for(auto [a,b]:o2) fa[a]=b;
		return;
	}
	solve(ls,l,mid,flag);
	solve(rs,mid+1,r,flag);
	reverse(o1.begin(),o1.end());
	reverse(o2.begin(),o2.end());
	for(auto [a,b]:o1) sz[a]=b;
	for(auto [a,b]:o2) fa[a]=b;
}
signed main(){
	read(n,Q);
	fo(i,1,2*n) fa[i]=i,sz[i]=1;
	fo(i,1,Q) {
		int x,y; read(x,y);
		if(mp.find({x,y})==mp.end())mp[{x,y}]=++tot,X[tot]=x,Y[tot]=y;
		int t=mp[{x,y}];
		opt[t]^=1;
		if(opt[t]==0) {
			add(1,1,Q,lst[t],i-1,x,y);
		}
		lst[t]=i;
	}
	fo(i,1,tot) if(opt[i]) add(1,1,Q,lst[i],Q,X[i],Y[i]);
	solve(1,1,Q,0);
}
posted @ 2025-01-10 21:49  dengchengyu  阅读(42)  评论(0)    收藏  举报