Loading

线段树分治学习笔记

线段树分治学习笔记

思想

对于下面这样一类问题

在一个时间轴上,有一系列有时间区间的操作,且有询问某个时间点上所有操作的贡献的一些询问

我们可以采用线段树分治,就是在时间轴上建立一棵线段树,把操作放在线段树的区间上,然后遍历线段树,执行操作,统计贡献,当递归到叶子节点时回答询问,回溯时再撤销操作.

最开始听不会太懂,做几道题基本上就会懂了


例题

线段树分治上维护数据结构有两个思路,一个是利用操作序变成树上dfs序也就是栈序,使用可撤销数据结构;另一个是利用树深度有限多次复用数据结构,具体来说就是维护 log 个数据结构,每次往下走都从父亲那里拷贝一份过来

by 夏色祭Official in luogu

不过现在只刷了可撤销并查集呢

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

题目大意是在长度为k的时间段里,n个点的图上有m条边,其中边 \(i\) 的存在时间为\(l_i-r_i\) ,求每个单位时间内图是否为二分图

判断图是否为二分图就是判断有没有奇环,这个可以用并查集以类似于食物链的方法来解决。在时间轴上建立并查集,对于存在一段时间的边,我们可以把它挂在线段树\(l_i-r_i\)的一些最大组成节点上,在回溯时用可撤销的并查集(按秩合并)来撤销加的边,走到一个节点判断图是否为奇环,最后到叶子节点输出答案即可。

注意若在一个节点已判断有奇环,则其子节点不用在进行操作,否则被 hack 掉

结合代码理解

#include<bits/stdc++.h>
using namespace std;
int const MAXN=2e5+10;
int n,m,k,cnt,tot,tott,_;
int f[MAXN],siz[MAXN];
vector<int>seg[MAXN<<2];
stack<int>st;
struct op{
	int u,v,be,en;
}e[MAXN];
void change(int x,int l,int r,int L,int R,int a){
	if(l<=L && R<=r){
		seg[x].push_back(a);
		return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)change(x<<1,l,r,L,mid,a);
	if(r>mid)change(x<<1|1,l,r,mid+1,R,a);
	return ;
}
int get(int x){
	while(x!=f[x])x=f[x];
	return x;
}
bool merge(int a,int b){
	bool flag=1;
	int fa=get(a),fb=get(b),fan=get(a+n),fbn=get(b+n);
	if(fa==fan || fb==fbn || fa==fb || fan==fbn)return 0;
	if(fa!=fbn){
		if(siz[fa]>siz[fbn])swap(fa,fbn);
		f[fa]=fbn;siz[fbn]+=siz[fa];st.push(fa);
	}
	if(fb!=fan){
		if(siz[fb]>siz[fan]) swap(fb,fan);
		f[fb]=fan;siz[fan]+=siz[fb];st.push(fb);
	}
	return 1;
}
void del(int sum){
	while(sum<st.size()){
		int x=st.top();st.pop();
		siz[f[x]]-=siz[x];f[x]=x;
	}
	return;
}
void query(int x,int L,int R,bool flag){
	int sum=st.size();
	for(int i=0;i<seg[x].size();i++){
		if(!flag)break;
		int a=e[seg[x][i]].u,b=e[seg[x][i]].v;
		if(!merge(a,b))flag=0;
	}
	if(L==R){
		if(flag)printf("Yes\n");
		else printf("No\n");
		del(sum);
		return;
	}
	int mid=(L+R)>>1;
	query(x<<1,L,mid,flag);query(x<<1|1,mid+1,R,flag);
	del(sum);
	return;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=2*n;i++)f[i]=i,siz[i]=1;
	for(int i=1;i<=m;i++){
		int x,y,l,r;
		scanf("%d%d%d%d",&x,&y,&l,&r);
		if(l==r)continue;
		e[i]=(op){x,y,l+1,r};
		change(1,l+1,r,1,k,i);
	}
	query(1,1,k,1);
	return 0;
}

2 P5214 [SHOI2014]神奇化合物

跟例1差不多的思路,挺板的。把边挂在线段树上,用可撤销的并查集维护分子关系

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<climits>
#include<string>
#include<deque>
#include<bitset>
#include<cstring>
#include<stack>
#include<cstdlib>
#include<iostream>
#include<ctime>
#include<algorithm>
using namespace std;
int const MAXN=4e5+10;
int n,m,tot,tott,cnt,_,q,ans;
int e[5001][5001],f[MAXN],siz[MAXN];
vector<int>seg[MAXN*4];
stack<int>sta;
struct edge{
	int a,b,st,en;
}op[MAXN];
int get(int x){
	while(x!=f[x])x=f[x];
	return x;
}
void insert(int x,int l,int r,int L,int R,int id){
	if(l<=L && R<=r){
		seg[x].push_back(id);
		return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)insert(x<<1,l,r,L,mid,id);
	if(r>mid)insert(x<<1|1,l,r,mid+1,R,id);
	return ;
}
void merge(int a,int b){
	int fa=get(a),fb=get(b);
	if(fa!=fb){
		if(siz[fa]>siz[fb])swap(fa,fb);
		f[fa]=fb;siz[fb]+=siz[fa];
		sta.push(fa);ans--;
	}
}
void del(int sum){
	while(sum<sta.size()){
		int x=sta.top();sta.pop();
		ans++;
		siz[f[x]]-=siz[x];f[x]=x;
	}
}
void query(int x,int L,int R){
	int sum=sta.size();
	for(int i=0;i<seg[x].size();i++){
		int a=op[seg[x][i]].a,b=op[seg[x][i]].b;
		merge(a,b);
	}
	if(L==R){
		printf("%d\n",ans);
		del(sum);
		return;
	}
	int mid=(L+R)>>1;
	query(x<<1,L,mid);query(x<<1|1,mid+1,R);
	del(sum);
	return;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		f[i]=i,siz[i]=1;
	}
	for(int i=1;i<=m;i++){
		cnt++;
		scanf("%d%d",&op[cnt].a,&op[cnt].b);
		e[op[cnt].a][op[cnt].b]=e[op[cnt].b][op[cnt].a]=cnt;
		op[cnt].st=1,op[cnt].en=0;
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		char c[10];int a,b;
		scanf("%s",c+1);
		if(c[1]=='A'){
			scanf("%d%d",&a,&b);
			e[a][b]=e[b][a]=++cnt;
			op[cnt].a=a,op[cnt].b=b;
			op[cnt].st=tot+1,op[cnt].en=0;
		}else if(c[1]=='D'){
			scanf("%d%d",&a,&b);
			op[e[a][b]].en=tot;
		}else if(c[1]=='Q')tot++;
	}
	for(int i=1;i<=cnt;i++){
		if(op[i].en==0)op[i].en=tot;
		insert(1,op[i].st,op[i].en,1,tot,i);
	}
	ans=n;
	query(1,1,tot);
	return 0;
}

3 CF576E Painting Edges

首道CF黑题!

这道题是另外一道题的加强版P5787 二分图 /【模板】线段树分治

现在问题在于如何维护修改,以及修改之后边的颜色的问题

考虑在在一条边上有两次修改\(a,b\),则在\((a+1,b-1)\)的时间内颜色要么是\(a\)修改的颜色,要么是\(a\)之前的一次修改的颜色(或无色),那么我们在a时刻假设修改成功,即可在那一时刻判断这次操作是否合法,决定\((a+1,b-1)\)这段时间这条边的颜色

所以现在我们需要维护一个支持撤销的并查集,于是选择按秩合并的并查集.边的颜色又会存在一段时间,便可以用线段树分治来维护

时间复杂度\(O(mlog_2nlog_2q)\)

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<climits>
#include<string>
#include<deque>
#include<bitset>
#include<cstring>
#include<stack>
#include<cstdlib>
#include<iostream>
#include<ctime>
#include<algorithm>
using namespace std;
int const MAXN=5e5+10;
int n,m,k,q,root,ans;
int f[60][MAXN<<1],vis[MAXN],siz[60][MAXN<<1],last[MAXN],cnt[MAXN],sati[MAXN];
vector<int>vec[MAXN<<2];
struct edge{
	int u,v;
}e[MAXN];
struct operation{
	int ei,ci,t;
}op[MAXN];
stack<pair<int,int> >S;
inline int get(int c,int x){
	while(x!=f[c][x])x=f[c][x];
	return x;
}
void merge(int c,int x,int y){
	int fx=get(c,x),fy=get(c,y),fxn=get(c,x+n),fyn=get(c,y+n);
	if(fx==fyn || fxn==fy)return;
	//if(cnt[c]==2*n){ans+=sati[c];}
	//cnt[c]-=2;
	if(siz[c][fx]>siz[c][fyn])swap(fx,fyn);
	siz[c][fyn]+=siz[c][fx],f[c][fx]=fyn,S.push(make_pair(fx,c));
	if(siz[c][fy]>siz[c][fxn])swap(fy,fxn);
	siz[c][fxn]+=siz[c][fy],f[c][fy]=fxn;S.push(make_pair(fy,c));
}
void insert(int x,int L,int R,int l,int r,int k){
	if(l<=L && R<=r){vec[x].push_back(k);return;}
	int mid=(L+R)>>1;
	if(l<=mid)insert(x<<1,L,mid,l,r,k);
	if(r>mid)insert(x<<1|1,mid+1,R,l,r,k);
	return;
}
void query(int x,int L,int R){
	int tag=S.size();
	for(int i=0;i<vec[x].size();i++){
		int id=vec[x][i];
		if(op[id].ci)merge(op[id].ci,e[op[id].ei].u,e[op[id].ei].v);
	}
	if(L==R){
		int u=e[op[L].ei].u,v=e[op[L].ei].v,c=op[L].ci;
		u=get(c,u),v=get(c,v);
		if(u==v){
			printf("NO\n");
			op[L].ci=last[op[L].ei];
		}else{
			last[op[L].ei]=c;
			printf("YES\n");
			//if(c)merge(c,u,v);
			//printf("%d %d %d\n",c,cnt[c],ans);
		}
	}else{
		int mid=(L+R)>>1;
		query(x<<1,L,mid);
		query(x<<1|1,mid+1,R);
	}
	while(tag<S.size()){
		int x=S.top().first,c=S.top().second;S.pop();
		siz[c][f[c][x]]-=siz[c][x];f[c][x]=x;
		//if(++cnt[c]==2*n)ans-=sati[c];
	}
}
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*10+c-'0';c=getchar();}
	return x*f;
}
int main(){
	root=1;
	n=read(),m=read(),k=read(),q=read();
	for(int i=1;i<=n;i++){
		cnt[i]=2*n;
		for(int c=1;c<=k;c++){
			f[c][i]=i,f[c][i+n]=i+n;
			siz[c][i]=siz[c][i+n]=1;
		}
	}
	for(int i=1;i<=m;i++){
		e[i].u=read(),e[i].v=read();
		last[i]=q+1;
	}
	//for(int i=1;i<=k;i++)sati[i]=read();
	for(int i=1;i<=q;i++){
		op[i].ei=read(),op[i].ci=read();
	}
	for(int i=q;i>=1;i--){
		int ei=op[i].ei;
		if(i<last[ei]-1){
			insert(root,1,q,i+1,last[ei]-1,i);
			last[ei]=i;
		}
	}
	memset(last,0,sizeof(last));
	query(root,1,q);
}

参考资料

posted @ 2020-10-18 17:08  fpjo  阅读(126)  评论(0编辑  收藏  举报