線段樹合併

線段樹合併,是一種聽起來很高大上的算法。

其意義是將兩棵線段樹合爲一棵,相同位置的信息合在一起。(通常爲權值線段樹)

實現不難,具體如下:

對於兩棵樹節點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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章