大根堆 HYSBZ - 4919 线段树合并

Part 0

​ 由这题,可以联想到最长不下降子序列。对于每个节点,我们定义DP[i]为这个子树内所有节点堆顶元素等于i时的最大的堆的大小。我们可以把这个数组放在线段树上维护。线段树的叶子节点存的就是DP[i]。线段树节点[L—R]存的是这段区间DP值的最大值。

Part 1

​ 线段树合并时就可以一直更新这个DP数组。由于每个子节点是互不干扰的,故合并时记录两颗线段树前面的DP最大值。(若当前操作区间为[L—R],那么我们分别记录两颗线段树内DP[1—L-1]的最大值,这可以作为参数向下传)若有一颗线段树为空,则把另一颗线段树DP[1—L-1]的最大值加上去(用懒惰标记)若两颗线段树有相同的叶子节点,那么直接合并即可(即DP数组相加,同时两个DP值要分别与[1—L]的DP值的最大值的取一个max)。

​ 加入当前节点的状态转移方程为DP[i]=MAX(DP[1—i-1])+1。同样是在Updata将DP[1—L]的最大值作为参数向下传。

​ 最后答案就是根节点的答案。

​ 注意初始对权值要离散化。

Part 2

​ 复杂度的话就是初始建树+线段树合并的复杂度。

AC代码:

#include<cstdio>
#include<algorithm>
#define M 200005
using namespace std; 
void check_max(int &x,int y){if(x<y)x=y;}
void check_min(int &x,int y){if(x>y)x=y;}
int id;
struct E{
	int to,nx;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b){
	edge[++tot].to=b;
	edge[tot].nx=head[a];
	head[a]=tot;
}
int tot_id;
int Root[M],Lson[M*20],Rson[M*20];
int mx[M*20],Add[M*20];
void Down(int tid){//懒惰标记 
	if(Lson[tid]){
		mx[Lson[tid]]+=Add[tid];
		Add[Lson[tid]]+=Add[tid];
	}
	if(Rson[tid]){
		mx[Rson[tid]]+=Add[tid];
		Add[Rson[tid]]+=Add[tid];
	}
	Add[tid]=0;
}
void Up(int tid){
	mx[tid]=max(mx[Lson[tid]],mx[Rson[tid]]);
}
void Updata(int L,int R,int x,int &tid,int Lmx=0){//Lmx为DP[1---L]的DP值的最大值 
	if(!tid)tid=++tot_id;//动点线段树 
	if(L==R){
		check_max(mx[tid],Lmx+1);
		return;
	}
	Down(tid);
	int mid=(L+R)>>1;
	if(x<=mid)Updata(L,mid,x,Lson[tid],Lmx);
	else Updata(mid+1,R,x,Rson[tid],max(Lmx,mx[Lson[tid]]));
	Up(tid);
}
int val[M],fa[M];
void Merge(int &x,int y,int L,int R,int XLmx=0,int YLmx=0){
//XLmx为X这颗线段树的DP[1---L]的最大值,YLmx为Y这颗线段树的DP[1---L]的最大值 
	if(!x&&!y)return;
	if(!y){
		mx[x]+=YLmx;
		Add[x]+=YLmx;//懒惰标记的更新 
		x+=y;
		return;
	}
	if(!x){
		mx[y]+=XLmx;
		Add[y]+=XLmx;
		x+=y;
		return;
	}
	if(L==R){
		check_max(XLmx,mx[x]);//先取max 
		check_max(YLmx,mx[y]);
		mx[x]=XLmx+YLmx;//再更新 
		return;
	}
	int mid=(L+R)>>1;
	Down(x),Down(y);
	Merge(Rson[x],Rson[y],mid+1,R,max(XLmx,mx[Lson[x]]),max(YLmx,mx[Lson[y]]));//只有在询问右区间时才更新XLmx与YLmx 
	Merge(Lson[x],Lson[y],L,mid,XLmx,YLmx);//这段语句必须在下面,因为上面更新XLmx与YLmx所需的都是老的mx数组中的值而非更新之后的。
	Up(x);
}
void dfs(int now){
	for(int i=head[now];i;i=edge[i].nx){
		int nxt=edge[i].to;
		if(nxt==fa[now])continue;
		dfs(nxt);
		Merge(Root[now],Root[nxt],1,id);//都先向上合并 
	}
	Updata(1,id,val[now],Root[now]);
}
int C[M];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&val[i],&fa[i]);
		Addedge(fa[i],i);
		C[i]=val[i];
	}
	sort(C+1,C+n+1);
	id=unique(C+1,C+n+1)-C-1;
	for(int i=1;i<=n;i++)val[i]=lower_bound(C+1,C+id+1,val[i])-C;//离散化 
	dfs(1);
	printf("%d\n",mx[Root[1]]);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值