线段树合并

线段树合并,是一种听起来很高大上的算法。

其意义是将两棵线段树合为一棵,相同位置的信息合在一起。(通常为权值线段树)

实现不难,具体如下:

对于两棵树节点x,y

1. 如果x,y其中有一个为空,返回另一个不为空的数的值
2. 否则新建一个节点,将两棵树的节点信息合并,然后递归左右子树继续合并,直到步骤1

很简单是吧?

看一下code:

int merge(int l,int r,int x,int y)
{
	if (!x||!y) return x+y;
	if (l==r)
	{
		tree[x]+=tree[y];return x;
	}
	int mid=(l+r)>>1;
	lson[x]=merge(l,mid,lson[x],lson[y]);
	rson[x]=merge(mid+1,r,rson[x],rson[y]);
	tree[x]=max(tree[lson[x]],tree[rson[x]]);
	return x;
}

一般来说,这样是不会超时的。

明显看出,其时间复杂度为两棵树的重合部分大小。

但是,权值线段树一般差异很大,因为其权值会有很大不同。

一般情况下,时间复杂度为O(能过)。

给两道题目练一下手

洛谷4556 雨天的尾巴 树上差分+线段树合并

code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int i,j,k,m,n,o,p,l,s,t,x,y,z,tot;
int f[400005][4],q[100005];
int tree[5000005],root[400005];
int b[100005][21],dep[100005],mi[21];
int ans[100005],lson[5000005],rson[5000005];
void insert(int x,int y)
{
	f[++t][1]=y,f[t][2]=q[x],q[x]=t;
}
void dg(int t,int fa)
{
	for (int k=q[t];k;k=f[k][2])
	{
		if (f[k][1]==fa) continue;
		b[f[k][1]][0]=t,dep[f[k][1]]=dep[t]+1;
		dg(f[k][1],t);
	}
}
int getlca(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	int k=dep[x]-dep[y];
	for (int i=20;i>=0;i--)
	{
		if (k>=mi[i]) k-=mi[i],x=b[x][i];	
	}	
	if (x==y) return x;
	for (int i=20;i>=0;i--)
	{
		if (b[x][i]!=b[y][i]) x=b[x][i],y=b[y][i];
	}
	return b[x][0];
} 
void maketree(int &x,int l,int r,int z,int add)
{
	if (!x) x=++tot; 
	if (l==r)
	{
		tree[x]+=add;return;
	}
	int mid=(l+r)>>1;
	if (z<=mid) maketree(lson[x],l,mid,z,add);
	else maketree(rson[x],mid+1,r,z,add); 
	tree[x]=max(tree[lson[x]],tree[rson[x]]); 
}
int merge(int l,int r,int x,int y)
{
	if (!x||!y) return x+y;
	if (l==r)
	{
		tree[x]+=tree[y];return x;
	}
	int mid=(l+r)>>1;
	lson[x]=merge(l,mid,lson[x],lson[y]);
	rson[x]=merge(mid+1,r,rson[x],rson[y]);
	tree[x]=max(tree[lson[x]],tree[rson[x]]);
	return x;
}
int query(int x,int l,int r)
{
	if (l==r) return l;
	int mid=(l+r)>>1;
	if (tree[x]==tree[lson[x]]) return query(lson[x],l,mid);
	else return query(rson[x],mid+1,r);
}
void dfs(int t,int fa)
{
	if (t==10)
	{
		int adf=0;
	}
	for (int k=q[t];k;k=f[k][2])
	{
		if (f[k][1]==fa) continue;
		dfs(f[k][1],t);
		root[t]=merge(1,100000,root[t],root[f[k][1]]);
	}
	if (!tree[root[t]]) ans[t]=0;
	else ans[t]=query(root[t],1,100000);
}
int main()
{
	freopen("Pique.in","r",stdin);
	freopen("Pique.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (i=1;i<=n-1;i++) scanf("%d%d",&o,&p),insert(o,p),insert(p,o);
	dg(1,0);mi[0]=1;
	for (i=1;i<=20;i++)
	{
		mi[i]=mi[i-1]*2;
		for (j=1;j<=n;j++) b[j][i]=b[b[j][i-1]][i-1];
	}
	for (i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		int lca=getlca(x,y);
		maketree(root[x],1,100000,z,1);
		maketree(root[y],1,100000,z,1);
		maketree(root[lca],1,100000,z,-1);
		maketree(root[b[lca][0]],1,100000,z,-1);
	}
	dfs(1,0);
	for (i=1;i<=n;i++) printf("%d\n",ans[i]);
	return 0;
}

另一道是JZOJ 3338 法法塔的奖励,大意是求树上的最长不下降子序列。

原题:

法法塔是很喜欢写程序的。所以冒着就算码农屌丝一辈子的风险也大无畏地写着程序。
码农们为了表彰法法塔的坚持与执着,决定给法法塔颁布奖励,为了体现码农的屌丝气质,奖励也将由法法塔自己做出选择!
所有的奖励被组织成了一棵树的形态,每个点都有一个权值。法法塔首先选择一个子树,然后选择从该子树内的一个叶子节点到该子树的根的一条路径,将路径上节点的权值依次排成一个序列,求得这个序列的最长不下降子序列长度,这个长度就是他能得到的奖品数目。要求该子树的根必须被选择。
接下来就是法法塔进行选择的时间了。尽可能多地得到奖品是自然的。那么法法塔到底能够得到多少奖品呢?这是个很纠结的问题,他决定交给你来解决。对于每个子树,他都希望你能求出他首先选择了这个子树后,他能得到的最多的奖品数目。
Input
第一行一个数n,描述树上节点的个数。
接下来一行n个数,描述树上每个点的父亲,点1为根,其父亲设为-1,其余每个点的父亲的标号都小于自己。
接下来一行n个数,表示树上每个点的权值。
Output
一行n个数,第i个数表示法法塔选择了以第i个节点为根的子树后,他可以获得的最多的奖品个数。
Sample Input
输入1:
2
-1 1
1 1
输入2:
6
-1 1 2 1 2 4
4 3 4 3 6 2
Sample Output
输出1:
2 1
输出2:
3 1 1 2 1 1
n<=100000,每个数的权值都是1到n的正整数。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int tree[2000001];
int bz[100005];
int f[100005][3],q[100005],a[100005],ans[100005],left[2000001],right[2000001],root[100005];
int i,j,k,m,n,o,p,l,s,t,tot;
void insert(int x,int y)
{
	f[++t][1]=y,f[t][2]=q[x],q[x]=t;
}
void change(int &x,int l,int r,int v,int z)
{
	if (!x) x=++tot;
	if (l==r) {
		tree[x]=max(tree[x],z);return;
	} else {
		int mid=(l+r)>>1;
		if (v<=mid) change(left[x],l,mid,v,z);
		else change(right[x],mid+1,r,v,z);
		tree[x]=max(tree[left[x]],tree[right[x]]);
	}
}
int query(int x,int l,int r,int st,int en)
{
	if (!x) return x;
	if (l==st&&r==en) return tree[x];
	else {
		int mid=(l+r)/2;
		if (en<=mid) return query(left[x],l,mid,st,en);
		else if (st>mid) return query(right[x],mid+1,r,st,en);
		else return max(query(left[x],l,mid,st,mid),query(right[x],mid+1,r,mid+1,en));
	}
}
int merge(int a,int b,int l,int r)
{
	if (!a||!b) return a+b;
	if (l==r)
	{
		tree[a]=max(tree[a],tree[b]);
		return a;
	} else {
		int mid=(l+r)/2;
		left[a]=merge(left[a],left[b],l,mid);
		right[a]=merge(right[a],right[b],mid+1,r);
		tree[a]=max(tree[a],tree[b]);
		return a;
	}
}
void work(int t)
{
	int g=0;
	for (int k=q[t];k;k=f[k][2])
	{
		work(f[k][1]);
		g=merge(g,root[f[k][1]],1,n);
	}
	ans[t]=query(g,1,n,1,a[t])+1;
	change(g,1,n,a[t],ans[t]);
	root[t]=g; 
}
int main()
{
	freopen("Henry.in","r",stdin);
	freopen("Henry.out","w",stdout);
	scanf("%d",&n);
	for (i=1;i<=n;i++) {
		scanf("%d",&o);
		if (o!=-1)insert(o,i);
	}
	for (i=1;i<=n;i++) scanf("%d",&a[i]);
	work(1);
	for (i=1;i<=n;i++) printf("%d ",ans[i]);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章