【BZOJ 4538】[Hnoi2016]网络 树链剖分+堆

236 篇文章 0 订阅
4 篇文章 0 订阅

很巧妙的一道题,因为是树上的操作,所以我们可以联想到树链剖分,但是存在这样的几个问题。

1.每一次查询都是查询不受影响的重要程度

2.每一个操作还可以撤回,当前的最大值在后面的操作中可能会撤回

3.懒惰标记的处理

主要就是这两个问题。

1.为了解决第一个问题,我们可以在每一次修改的时候修改当前需要修改的区间的补集,也就是说线段树维护的就是不经过改点的最大重要度,也就是该点被破坏以后不会受到影响的最大重要度

2.为了解决第二个问题,我们可以在线段树的每一个节点开一个堆,维护这一段区间的重要度。删除的话再开一个就好了。

3.这个嘛,就不要下放标记了,反正是最大值,永久化标记就好了

ps:

1.set会T,安心用堆吧

2.树剖if(size[v]>=size[son[u]])son[u]=v;因为专门构造了数据来卡,所以>会T

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#define ls u<<1,l,mid
#define rs u<<1|1,mid+1,r
#define maxn 200021
using namespace std;
priority_queue<int>s[maxn*2],nd[maxn*2];
pair<int,int>sta[maxn*2];
int n,m,top[maxn],f[maxn],cur[maxn],nu[maxn],cnt,tot=1,head[maxn];
struct PBH{int a,b,c;}P[maxn];
int size[maxn],h[maxn],son[maxn],tp;
struct edge{int v,next;}e[maxn];
void adde(int a,int b){e[tot].v=b,e[tot].next=head[a];head[a]=tot++;}
void dfs1(int u,int fa){
	size[u]=1,f[u]=fa,h[u]=h[fa]+1;
	for(int v,i=head[u];i;i=e[i].next){
		if((v=e[i].v)==fa)continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>=size[son[u]])son[u]=v;
	}
}
void dfs2(int u,int fa,int tt){
	cur[nu[u]=++cnt]=u;top[u]=tt;
	if(son[u])dfs2(son[u],u,tt);
	for(int v,i=head[u];i;i=e[i].next){
		if((v=e[i].v)==fa||v==son[u])continue;
		dfs2(v,u,v);
	}
}
void update(int u,int l,int r,int x,int y,int add){
	if(x==l&&r==y){
		if(add>0)s[u].push(add);
		else nd[u].push(-add);
		return ; 
	}int mid=l+r>>1;
	if(x>mid)update(rs,x,y,add);
	else if(y<=mid)update(ls,x,y,add);
	else update(ls,x,mid,add),update(rs,mid+1,y,add);
}
int query(int u,int l,int r,int x){
	while(!nd[u].empty()&&s[u].top()==nd[u].top())s[u].pop(),nd[u].pop();
	int ans=s[u].empty() ? -1 : s[u].top();
	if(l==r)return ans;
	int mid=l+r>>1;
	if(x>mid)ans=max(ans,query(rs,x));
	else ans=max(ans,query(ls,x));
	return ans;
}
int change(int a,int b,int add){
	tp=0;
	while(top[a]!=top[b]){
		if(h[top[a]]>h[top[b]])swap(a,b);
		sta[++tp]=make_pair(nu[top[b]],nu[b]);
		b=f[top[b]];
	}
	if(h[a]>h[b])swap(a,b);
	sta[++tp]=make_pair(nu[a],nu[b]);
	sort(sta+1,sta+1+tp);
	int last=1,now;
	for(int i=1;i<=tp;i++){
		now=sta[i].first-1;
		if(last<=now)update(1,1,n,last,now,add);
		last=sta[i].second+1;
	}if(last<=n)update(1,1,n,last,n,add);
}

int main(){
	scanf("%d%d",&n,&m);
	for(int a,b,i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		adde(a,b),adde(b,a);
	}dfs1(1,0);dfs2(1,1,1);
	int pos,a,b,c;
	for(int i=1;i<=m;i++){
		scanf("%d",&pos);
		if(pos==0){
			scanf("%d%d%d",&a,&b,&c);
			P[i]=(PBH){a,b,c};
			change(a,b,c);
		}else if(pos==2){
			scanf("%d",&a);
			printf("%d\n",query(1,1,n,nu[a]));
		}else{
			scanf("%d",&a);
			change(P[a].a,P[a].b,-P[a].c);
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值