线段树合并经典例题(3)

子树限定距离的最大值,最小值,和维护

链接:A-智乃酱的子树查询类问题_牛客竞赛数据结构专题班dsu on tree、长链剖分习题 (nowcoder.com)
题意:给定一棵树,有点权,以 1 为根。多次询问,每次询问点 x 的子树内距离 x 的距离在 [ l , r ] [l,r] [l,r] 内的所有点的权值最小值,最大值,权值和。

题解:启发式合并无法删点之后动态快速更新最值,因此采用线段树合并来做。先将所有询问离线,当访问到该点时才计算答案。这样从根搜索出去后,每次将子节点的树合并到父节点上,只会有加点操作,因此可以动态的维护最值,权值和。每个节点下开的是一棵动态开点的权值线段树,权值就是距离 1 节点的距离,这样对于 x 距离 [ l , r ] [l,r] [l,r] 的点即为在 [ d e p [ x ] + l , d e p [ x ] + r ] [dep[x]+l,dep[x]+r] [dep[x]+l,dep[x]+r] 线段树权值范围内的点。

#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<functional>
#include<queue>
#include<unordered_map>
#include<map>
#include<set>

using namespace std;
using ll=long long;
using P=pair<int,int>;
const int inf=1e9;

struct Merge{
	static const int N=1e5+5;
	int n,now;
	vector<int>rt,ls,rs,mx,mn;
	vector<ll>sm;
	Merge(int x=N):n((x+5)*50),now(0),rt(x+5),ls(n),rs(n),sm(n),mx(n),mn(n,inf){}

	void pushup(int k){
		int l=ls[k],r=rs[k];
		sm[k]=sm[l]+sm[r];
		mx[k]=max(mx[l],mx[r]);
		mn[k]=min(mn[l],mn[r]);
	}

	void update(int&u,int l,int r,int pos,int w){
		if(!u)u=++now;
		if(l==r)
		{
			sm[u]+=w;
			mx[u]=max(mx[u],w);
			mn[u]=min(mn[u],w);
			return;
		}
		int mid=l+r>>1;
		if(pos<=mid)update(ls[u],l,mid,pos,w);
		else update(rs[u],mid+1,r,pos,w);
		pushup(u);
	}

	void merge(int&u,int&v,int l,int r){
		if(!u||!v){u=u|v; return;}
		if(l==r)
		{	
			mx[u]=max(mx[u],mx[v]);
			mn[u]=min(mn[u],mn[v]);
			sm[u]+=sm[v];
			return;
		}
		int mid=l+r>>1;
		merge(ls[u],ls[v],l,mid);
		merge(rs[u],rs[v],mid+1,r);
		pushup(u);
	}

	ll sum,mxx,mnn;

	void query(int u,int l,int r,int ql,int qr){
		if(ql<=l&&r<=qr)
		{
			sum+=sm[u];
			mxx=max(mxx,(ll)mx[u]);
			mnn=min(mnn,(ll)mn[u]);
			return;
		}
		int mid=l+r>>1;
		if(ql<=mid)query(ls[u],l,mid,ql,qr);
		if(mid<qr)query(rs[u],mid+1,r,ql,qr);
	}

};

void solve()
{
	int n; cin>>n;
	vector<int>a(n+1);
	vector<vector<int>>ed(n+1);
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1,u,v;i<n;i++)
	{
		cin>>u>>v;
		ed[u].push_back(v);
		ed[v].push_back(u);
	}

	int m; cin>>m;
	struct node{
		int l,r,id;
	};
	vector<vector<node>>q(n+1);
	vector<array<ll,3>>ans(m+1);
	for(int i=1,u,v,w;i<=m;i++)
	{
		cin>>u>>v>>w;
		q[u].push_back({v,w,i});
	}

	Merge tr(n);
	vector<int>dep(n+1);

	auto dfs=[&](auto dfs,int x,int fa)->void{
		dep[x]=dep[fa]+1;
		tr.update(tr.rt[x],1,n,dep[x],a[x]);
		for(auto y:ed[x])
		{
			if(y==fa)continue;
			dfs(dfs,y,x);
			tr.merge(tr.rt[x],tr.rt[y],1,n);
		}
		for(auto[l,r,id]:q[x])
		{
			tr.sum=tr.mxx=0,tr.mnn=1e9;
			tr.query(tr.rt[x],1,n,dep[x]+l,dep[x]+r);
			ans[id]={tr.mnn,tr.mxx,tr.sum};
		}
	};

	dfs(dfs,1,0);
	for(int i=1;i<=m;i++)
	{
		auto[mn,mx,sm]=ans[i];
		cout<<mn<<" "<<mx<<" "<<sm<<"\n";
	}

}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int t=1; //cin>>t;
	while(t--)solve();
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值