线段树合并

Posted ticmis

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树合并相关的知识,希望对你有一定的参考价值。

0.1

诶嘿,好像鸽了太久了,博客的访问速度都明显变慢了ヽ(゜▽゜ )-C<(/;◇;)/~

1.1 线段树合并

当你有两个数组时,并且希望快速合并两个数组时,最朴实的想法莫过于:枚举、合并,吧。

for(int i=1;i<=n;++i) a[i]+=b[i];

复杂度显然是(O(n))的。那么对于一个空间复杂度为(O(n))的数组,时间复杂度似乎很难再低于(O(n))了,那..线段树合并的复杂度是..(O(mlog n))

Tips: 线段树合并时,需要佩套使用“动态开点”。

有两棵线段树(a)(b),合并后的树为(c)。对于相同位置(id),如果任意一棵树不存在这个结点,那么(c)的对应位置就可以直接复制为另一棵树的结点;

	if(!lson[idx]||!lson[idy]) lson[idx]=(lson[idx]|lson[idy]);
	else ...;
	if(!rson[idx]||!rson[idy]) rson[idx]=(rson[idx]|rson[idy]);
	else ...;

如果两棵树共同拥有这个结点呢?那就老老实实的递归下去。

	if(!lson[idx]||!lson[idy]) ...;
	else merge(lson[idx],lson[idy],l,mid);
	if(!rson[idx]||!rson[idy]) ...;
	else merge(rson[idx],rson[idy],mid+1,r);

递归的尽头是什么?自然是当(l=r)时。此时,我们再赋值即可。

	if(l==r) {num[idx]+=num[idy]; return;}

完整起来就是这样的:

void merge(int idx,int idy,int l,int r){
	if(l==r) {num[idx]+=num[idy]; return;}
	int mid=(l+r)>>1;
	if(!lson[idx]||!lson[idy]) lson[idx]=(lson[idx]|lson[idy]);
	else merge(lson[idx],lson[idy],l,mid);
	if(!rson[idx]||!rson[idy]) rson[idx]=(rson[idx]|rson[idy]);
	else merge(rson[idx],rson[idy],mid+1,r);
}

1.2 线段树合并的复杂度分析

最劣情况

emm..感觉好没必要啊..有可能白忙了一大通,结果最后还是递归到了尽头。复杂度白添了一个(O(log n))

是的,在最劣情况下,复杂度的确变成了(O(nlog n))。但是想想,这其实只是常数级别的劣化;如果时间不吃紧的话,这个劣化仍然是可以接受的。

最优情况

但是..具体而言,什么情况下,单次合并才会退化到(O(log n)):当两个结点都曾经被赋过值时,才会一直递归下去。所以正确的复杂度其实是:((m log n))(m)指两数组中均被赋值的个数。

那么如果(a)(b)都是比较稀疏的数组:(m)是很小的,总复杂度就会变小。当两个数组几乎没有被改变过时,复杂度可以直逼常数级别!ヾ(≧▽≦*)o

平均情况

实践表明,在值域(O(10^5)),插入(O(10^5))时,时限(1s)还是不虚的。合并(n)个长度为(n)的数组时可以认为是(O(nlog n))的。

2.1 [Vani有约会]雨天的尾巴

P4556 [Vani有约会]雨天的尾巴

当你自认为掌据线段树合并时,可以用这道题来检验一下。

其实说来也简单:对于每一种补给,在(x)(y)(+1),在(lca(x,y))(fa[lca(x,y)])(-1);然后用线段树合并,利用一下线段树的强大功能顺便维护一下最多补给编号即可。

注意事项

(1) 将(b)合并到(a)后,(a)(b)的底端结单合并起来了。因此再次更改(a)时,(b)也受到了牵连。因此记得实时更新答案

(2) 由于线段树需要动态开点,因此注意当前处理的是否被拓展过

if(!id) id++;
#include <bits/stdc++.h>
using namespace std;
#define lor(a,b,c) for(register int a=b;a<=c;++a)
#define ror(a,b,c) for(register int a=c;a>=b;--a)
#define tor(a,b) for(register int a=head[b];a;a=nxt[a])

const int MAXN=1e5+5,MAXM=1e5+5,TOP=1e5,MAXD=8e6+5;

int n,m;
int ecnt,head[MAXN],edge[MAXN<<1],nxt[MAXN<<1];
int fa[MAXN][17],dep[MAXN];
int x[MAXM],y[MAXM],z[MAXM],lca[MAXM];
int tot,root[MAXN],lson[MAXD],rson[MAXD],num[MAXD],max_num[MAXD],max_id[MAXD];
int ans[MAXN];

inline void insert(int,int,int);
void dfs_init(int,int);
inline int get_lca(int a,int b);
inline void updata(int);
int query_num(int,int,int,int);
void modify(int&,int,int,int,int);
void merge(int,int,int,int);
void dfs(int,int);

int main(){
	#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	#endif
	scanf("%d%d",&n,&m);

	lor(i,1,n-1){
		int a,b; scanf("%d%d",&a,&b);
		insert(a,b,++ecnt); insert(b,a,++ecnt);
	}
	dfs_init(1,1);
	lor(i,1,m){
		scanf("%d%d%d",&x[i],&y[i],&z[i]); lca[i]=get_lca(x[i],y[i]);
		modify(root[x[i]],1,TOP,z[i],1); modify(root[y[i]],1,TOP,z[i],1);
		modify(root[lca[i]],1,TOP,z[i],-1);
		if(fa[lca[i]][0]!=lca[i]) modify(root[fa[lca[i]][0]],1,TOP,z[i],-1);
	}

	dfs(1,1);
	lor(i,1,n) printf("%d
",ans[i]);

	return 0;
}

inline void insert(int from,int to,int id){
	nxt[id]=head[from]; head[from]=id; edge[id]=to;
}

void dfs_init(int u,int f){
	fa[u][0]=f; dep[u]=dep[f]+1;
	lor(i,1,15) fa[u][i]=fa[fa[u][i-1]][i-1];
	tor(i,u){
		int v=edge[i]; if(v==f) continue;
		dfs_init(v,u);
	}
}

inline int get_lca(int a,int b){
	if(dep[a]<dep[b]) swap(a,b);
	ror(i,0,15) if(dep[fa[a][i]]>=dep[b]) a=fa[a][i]; if(a==b) return a;
	ror(i,0,15) if(fa[a][i]!=fa[b][i]) {a=fa[a][i]; b=fa[b][i];} return fa[a][0];
}

inline void updata(int id){
	if(!lson[id]||!rson[id])
		{max_num[id]=max_num[lson[id]|rson[id]]; max_id[id]=max_id[lson[id]|rson[id]];
	}
	if(max_num[lson[id]]>=max_num[rson[id]]){
		max_num[id]=max_num[lson[id]]; max_id[id]=max_id[lson[id]];
	}
	else{
		max_num[id]=max_num[rson[id]]; max_id[id]=max_id[rson[id]];
	}
}

int query_num(int id,int l,int r,int p){
	if(!id) return 0;
	if(l==r) return num[id];
	int mid=(l+r)>>1;
	if(p<=mid) return query_num(lson[id],l,mid,p);
	else return query_num(rson[id],mid+1,r,p);
}

void modify(int &id,int l,int r,int p,int k){
	if(!id) id=++tot;
	if(l==r) {num[id]+=k; max_num[id]=num[id]; max_id[id]=num[id]>0?l:0; return;}
	int mid=(l+r)>>1;
	if(p<=mid) modify(lson[id],l,mid,p,k);
	else modify(rson[id],mid+1,r,p,k);
	updata(id);
}


void merge(int idx,int idy,int l,int r){
	if(l==r) {num[idx]+=num[idy]; max_num[idx]=num[idx]; max_id[idx]=num[idx]>0?l:0; return;}
	int mid=(l+r)>>1;
	if(!lson[idx]||!lson[idy]) lson[idx]=(lson[idx]|lson[idy]);
	else merge(lson[idx],lson[idy],l,mid);
	if(!rson[idx]||!rson[idy]) rson[idx]=(rson[idx]|rson[idy]);
	else merge(rson[idx],rson[idy],mid+1,r);
	updata(idx);
}

void dfs(int u,int f){
	tor(i,u){
		int v=edge[i]; if(v==f) continue;
		dfs(v,u);
		if(!root[u]) root[u]=++tot;
		merge(root[u],root[v],1,TOP);
	}
	ans[u]=max_id[root[u]];
}

2.2 天天爱跑步

P1600 天天爱跑步

据说用线段树合并可以得到(80pts),但并没有写过

以上是关于线段树合并的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ 4756 线段树合并(线段树)

线段树合并

线段树区间合并POJ3667-Hotel

线段树维护区间合并——cf1285E

Luogu4556 雨天的尾巴 树上差分线段树合并

bzoj2733 [ HNOI2012 ] -- 并查集+线段树合并