线段树合并,是一种听起来很高大上的算法。
其意义是将两棵线段树合为一棵,相同位置的信息合在一起。(通常为权值线段树)
实现不难,具体如下:
对于两棵树节点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;
}