用户工具

站点工具


2020-2021:teams:legal_string:jxm2001:线段树合并_分裂

这是本文档旧的修订版!


线段树合并

算法简介

一种合并多个线段树(一般为权值线段树)的算法,主要用于解决染色问题,时空间复杂度 $O(m\log n)$。

算法思想

更新线段树时动态开点,合并时如果遇到叶子节点或空结点就直接 $\text{return}$,否则跑子树。

代码模板

查看代码

查看代码

const int MAXS=MAXN*60;
int root[MAXN],tot;
struct Node{
	int max_cnt,ans;//自己需要维护的信息
	int ch[2];
}node[MAXS];
void push_up(int k){//自定义
	if(node[node[k].ch[0]].max_cnt>=node[node[k].ch[1]].max_cnt)
	node[k].max_cnt=node[node[k].ch[0]].max_cnt,node[k].ans=node[node[k].ch[0]].ans;
	else
	node[k].max_cnt=node[node[k].ch[1]].max_cnt,node[k].ans=node[node[k].ch[1]].ans;
}
void update(int &k,int lef,int rig,int pos,int v){
	if(!k) k=++tot;
	if(lef==rig) return node[k].max_cnt+=v,node[k].ans=pos,void();
	int mid=lef+rig>>1;
	if(pos>mid)
	update(node[k].ch[1],mid+1,rig,pos,v);
	else
	update(node[k].ch[0],lef,mid,pos,v);
	push_up(k);
}
void Merge(int &k1,int k2,int lef,int rig){
	if(!k1||!k2) return k1|=k2,void();
	if(lef==rig) return node[k1].max_cnt+=node[k2].max_cnt,void();
	int mid=lef+rig>>1;
	Merge(node[k1].ch[0],node[k2].ch[0],lef,mid);
	Merge(node[k1].ch[1],node[k2].ch[1],mid+1,rig);
	push_up(k1);
}

算法练习

习题一

题意

给定一棵 $n$ 个节点的数,$m$ 个操作。

每个操作三个参数 $x,y,z$,表示给结点 $x$ 到 $y$ 的树链上的每个点打上一个 $z$ 号标记。

经过所有操作后输出每个结点被打上的最多的标记的编号(满足条件的标记存在多个时输出编号最小的),如果该结点未被标记过,输出 $0$。

题解 1

离散化处理标记编号,防止 $\text{MLE}$。

每个结点用一棵权值线段树维护该结点的所有标记状态,树上差分打标记,最后从叶子结点开始向上合并即可。

查看代码

查看代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <sstream>
#include <cstring>
#include <cctype>
#include <cmath>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <ctime>
#include <cassert>
#define _for(i,a,b) for(int i=(a);i<(b);++i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
inline int read_int(){
	int t=0;bool sign=false;char c=getchar();
	while(!isdigit(c)){sign|=c=='-';c=getchar();}
	while(isdigit(c)){t=(t<<1)+(t<<3)+(c&15);c=getchar();}
	return sign?-t:t;
}
inline LL read_LL(){
	LL t=0;bool sign=false;char c=getchar();
	while(!isdigit(c)){sign|=c=='-';c=getchar();}
	while(isdigit(c)){t=(t<<1)+(t<<3)+(c&15);c=getchar();}
	return sign?-t:t;
}
inline char get_char(){
	char c=getchar();
	while(c==' '||c=='\n'||c=='\r')c=getchar();
	return c;
}
inline void write(LL x){
	register char c[21],len=0;
	if(!x)return putchar('0'),void();
	if(x<0)x=-x,putchar('-');
	while(x)c[++len]=x%10,x/=10;
	while(len)putchar(c[len--]+48);
}
inline void space(LL x){write(x),putchar(' ');}
inline void enter(LL x){write(x),putchar('\n');}
const int MAXN=1e5+5,MAXM=20;
struct Edge{
	int to,next;
}edge[MAXN<<1];
int head[MAXN],edge_cnt;
void Insert(int u,int v){
	edge[++edge_cnt].to=v;
	edge[edge_cnt].next=head[u];
	head[u]=edge_cnt;
}
struct LCA{
	int d[MAXN],anc[MAXN][MAXM],log2[MAXN];
	void dfs(int u,int fa,int dep){
		anc[u][0]=fa,d[u]=dep;
		for(int i=head[u];i;i=edge[i].next){
			int v=edge[i].to;
			if(v==fa)
			continue;
			dfs(v,u,dep+1);
		}
	}
	void build(int root,int n){
		log2[1]=0;
		_rep(i,2,n)
		log2[i]=log2[i>>1]+1;
		dfs(root,-1,1);
		_rep(i,1,n){//下标从1开始 
			for(int j=1;(1<<j)<n;j++)
			anc[i][j]=-1;
		}
		for(int j=1;(1<<j)<n;j++){
			_rep(i,1,n){
				if(anc[i][j-1]==-1)
				continue;
				anc[i][j]=anc[anc[i][j-1]][j-1];
			}
		}
	}
	int query(int p,int q){
		if(d[p]<d[q])
		swap(p,q);
		for(int i=log2[d[p]];i>=0;i--){
			if(d[p]-(1<<i)>=d[q])
			p=anc[p][i];
		}
		if(p==q)
		return p;
		for(int i=log2[d[p]];i>=0;i--){
			if(anc[p][i]!=-1&&anc[p][i]!=anc[q][i]){
				p=anc[p][i],q=anc[q][i];
			}
		}
		return anc[p][0];
	}
}lca;
const int MAXS=MAXN*60;
int root[MAXN],tot;
struct Node{
	int max_cnt,ans;
	int ch[2];
}node[MAXS];
void push_up(int k){
	if(node[node[k].ch[0]].max_cnt>=node[node[k].ch[1]].max_cnt)
	node[k].max_cnt=node[node[k].ch[0]].max_cnt,node[k].ans=node[node[k].ch[0]].ans;
	else
	node[k].max_cnt=node[node[k].ch[1]].max_cnt,node[k].ans=node[node[k].ch[1]].ans;
}
void update(int &k,int lef,int rig,int pos,int v){
	if(!k) k=++tot;
	if(lef==rig) return node[k].max_cnt+=v,node[k].ans=pos,void();
	int mid=lef+rig>>1;
	if(pos>mid)
	update(node[k].ch[1],mid+1,rig,pos,v);
	else
	update(node[k].ch[0],lef,mid,pos,v);
	push_up(k);
}
void Merge(int &k1,int k2,int lef,int rig){
	if(!k1||!k2) return k1|=k2,void();
	if(lef==rig) return node[k1].max_cnt+=node[k2].max_cnt,void();
	int mid=lef+rig>>1;
	Merge(node[k1].ch[0],node[k2].ch[0],lef,mid);
	Merge(node[k1].ch[1],node[k2].ch[1],mid+1,rig);
	push_up(k1);
}
int X[MAXN],Y[MAXN],Z[MAXN],b[MAXN],ans[MAXN],Max_z;
void dfs(int u){
	for(int i=head[u];i;i=edge[i].next){
		int v=edge[i].to;
		if(lca.anc[u][0]==v)
		continue;
		dfs(v);
		Merge(root[u],root[v],1,Max_z);
	}
	ans[u]=node[root[u]].max_cnt>0?node[root[u]].ans:0;
}
int main()
{
	int n=read_int(),q=read_int(),u,v,p;
	_for(i,1,n){
		u=read_int(),v=read_int();
		Insert(u,v);
		Insert(v,u);
	}
	lca.build(1,n);
	_for(i,0,q)
	X[i]=read_int(),Y[i]=read_int(),Z[i]=read_int();
	memcpy(b,Z,sizeof(Z));
	sort(b,b+q);
	Max_z=unique(b,b+q)-b;
	_for(i,0,q){
		Z[i]=lower_bound(b,b+Max_z,Z[i])-b+1;
		update(root[X[i]],1,Max_z,Z[i],1);update(root[Y[i]],1,Max_z,Z[i],1);
		p=lca.query(X[i],Y[i]);
		update(root[p],1,Max_z,Z[i],-1);
		p=lca.anc[p][0];
		if(p!=-1)
		update(root[p],1,Max_z,Z[i],-1);
	}
	dfs(1);
	_rep(i,1,n)
	if(ans[i])
	enter(b[ans[i]-1]);
	else
	enter(0);
	return 0;
}

题解 2

考虑树剖,同样也是差分打标记,时间复杂度 $O(m\log^2 n)$,空间复杂度 $O(n)$。

查看代码

查看代码

 
2020-2021/teams/legal_string/jxm2001/线段树合并_分裂.1594008197.txt.gz · 最后更改: 2020/07/06 12:03 由 jxm2001