线段树分治

处理的问题

依次操作,有增加,删除,对时间点询问几种操作的问题。

有时候删除有限制(如可撤销并查集),所以就不考虑删除,只考虑每一个增加操作存在的时间范围。

有时候问题中没有明说删除,比如最小mex生成树,而是需要不使用某些边。

可撤销并查集

并查集可以按秩合并,合并/查询单次时间复杂度 \(O(\log N)\)

可以用一个栈存上次合并的操作,撤销上一步的操作即将栈顶的依次合并取消,并弹出栈顶。

pair<int,int>merge(int x,int y){
	while(x!=Fa[x])x=Fa[x];
	while(y!=Fa[y])y=Fa[y];
	if(x==y)return {0,0};
	if(sz[x]<sz[y]){
		Fa[x]=y;
		sz[y]+=sz[x];
		return {x,y};
	}
	else{
		Fa[y]=x;
		sz[x]+=sz[y];
		return {y,x};
	}
}
stack<pair<int,int> >del;
void revoke(){
	pair<int,int>last=del.top();
	del.pop();
	int x=last.first,y=last.second;
	Fa[x]=x;
	sz[y]-=sz[x];
}
signed main(){
	//...
	del.push(merge(x,y));
}

写法

线段树分治中以时间作为下标,将每个操作存在的时间区间分为至多 \(\log N\) 个不重叠的小区间。

以动态图连通性为例,线段树上维护每条边的出现时间段。

由于询问的是时间点,所以找答案时必定会递归到叶子结点。

递归到线段树上一个点时,将这个点对应时间段的边加入并查集,离开时再删除,满足可撤销并查集只能删上一个的限制。

#include<bits/stdc++.h>
#define lc p<<1
#define rc p<<1|1
#define endl putchar('\n')
#define psp putchar(' ')
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=2e5+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
void putstr(string s){
	for(int i=0;i<s.size();i++)putchar(s[i]);
}
int lowbit(int x){
	return x&-x;
}
int n,m,k;
int T;
struct node{
	int l,r;
	vector<pair<int,int> >add;
}f[4*N];
void build(int p,int l,int r){
	f[p]=node{l,r};
	f[p].add.clear();
	if(l==r)return;
	int mid=l+r>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
}
int sz[N];
int Fa[N];
void update(int p,int l,int r,int u,int v){
	if(l<=f[p].l&&f[p].r<=r){
		f[p].add.push_back({u,v});
		return;
	}
	int mid=f[p].l+f[p].r>>1;
	if(l<=mid)update(lc,l,r,u,v);
	if(r>mid)update(rc,l,r,u,v);
}
pair<int,int>merge(int x,int y){
	while(x!=Fa[x])x=Fa[x];
	while(y!=Fa[y])y=Fa[y];
	if(x==y)return {0,0};
	if(sz[x]<sz[y]){
		Fa[x]=y;
		sz[y]+=sz[x];
		return {x,y};
	}
	else{
		Fa[y]=x;
		sz[x]+=sz[y];
		return {y,x};
	}
}
int ans[N];
stack<pair<int,int> >del;
void dfs(int p,int is){
	int before=del.size();
	for(int i=0;i<f[p].add.size();i++){
		int u=f[p].add[i].first;
		int v=f[p].add[i].second;
		while(u!=Fa[u])u=Fa[u];
		while(v!=Fa[v])v=Fa[v];
		if(u==v)is=0;
		else{
			del.push(merge(u,v+n));
			del.push(merge(v,u+n));
		}
	}
	if(f[p].l==f[p].r){
		ans[f[p].l]=is;
	}
	else{
		dfs(lc,is);
		dfs(rc,is);
	}
	while(del.size()>before){
		int u=del.top().first;
		int v=del.top().second;
		Fa[u]=u;
		sz[v]-=sz[u];
		del.pop();
	}
}
signed main(){
	//ios::sync_with_stdio(0);
	n=read(),m=read(),k=read();
	build(1,1,k);
	for(int i=1;i<=2*n;i++)Fa[i]=i,sz[i]=1;
	while(m--){
		int u=read(),v=read(),l=read(),r=read();
		if(l>r)swap(l,r);
		if(l+1<=r)update(1,l+1,r,u,v);
	}
	dfs(1,1);
	for(int i=1;i<=k;i++){
		if(ans[i])putstr("Yes\n");
		else putstr("No\n");
	}
}

做题时

要先找到题目中的区间怎么分配,比如:在时间 \([L,R]\) 存在,其他时间不存在;在颜色为 \(C\) 时不存在,其余时间存在;在权值为 \(X\) 时不存在,其余存在……

自己遇到的问题

  1. 很久没写多不是动态开点的线段树了,线段树要记得 build
  2. 栈记得 pop
  3. 合并和撤销的时候两个点不要搞反了。

经典例题

用户要求我收集线段树分治的经典例题,并且以链接的形式生成。为了全面获取信息,我将同时进行多项搜索,涵盖不同关键词和可能的中文资源。
搜索到 33 个网页
搜索结果显示了一些相关页面。为了全面收集信息,我需要同时打开这些页面。
这些资料提供了许多例题。我的回答将整理出一份经典例题列表,并附上简要说明和来源。我还会提到一些相关的题单和资源。我的回答会分类列出这些例题,比如动态图连通性、二分图判定、线性基、最短路等。现在开始组织回答。
现在我需要将这些经典例题以链接的形式生成:

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

P5631 最小mex生成树

CF1681F Unique Occurrences

大融合

P5227 [AHOI2013] 连通图

CF576E Painting Edges

posted on 2026-07-14 21:17  fish2012  阅读(3)  评论(0)    收藏  举报