P2590 [ZJOI2008]树的统计——解题报告

一、题目链接:

P2590 [ZJOI2008]树的统计

二、题目大意:

一棵树上有 n n n个节点,编号分别为 1 ∼ n 1\sim n 1n,每个节点都有一个权值 w w w。我们将以下面有 q q q个询问,要求你对这棵树完成一些操作:

  • CHANGE u t : 把结点 u u u的权值改为 t t t
  • QMAX u v: 询问从点 u u u到点 v v v的路径上的节点的最大权值。
  • QSUM u v: 询问从点 u u u到点 v v v的路径上的节点的权值和。

数据范围: 1 ≤ n ≤ 3 × 1 0 4 1\leq n\leq 3×10^4 1n3×104 0 ≤ q ≤ 2 × 1 0 5 0\leq q\leq 2×10^5 0q2×105

三、题目分析

  • 题目的意思非常简单明了,很明显是一道类似于线段树的题目。但是这个时候我们遇到的麻烦就是,线段树一般是在一段连续区间上进行的,而本题的数据在一棵树上,这对我们使用线段树带来了麻烦。
  • 因为题目每一次修改都是在某一条路径上进行的,所以这提醒我们可以将树上某一条链上看作连续的一段数据,这为我们使用线段树创造了条件。但同时,如果选择一条链为连续数据,那么就意味着剩下的点将不是连续的数据,所以如何选择这条链成为了关键问题。
  • 为了降低时间复杂度,我们应当选择子节点最多的点来构成这条链,即树链剖分算法。所以我们可以使用树链剖分的方式来维护这些数据,将重链上的点重新进行编号,让它们作为连续的一段数据,使得我们可以用线段树来维护区间和和区间最大值。
  • 当询问区间值的时候,我们将区间切割为不同树链的组合,分段来得到答案即可。

四、正解程序:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define inf 1e15

using namespace std;
typedef long long ll;
const ll maxn=30010;
struct node
{
	ll v;
	ll next;
}e[maxn*2];
ll p[maxn],t=0;
ll n,val[maxn];
ll d[maxn],fa[maxn],size[maxn],son[maxn];
void insert(ll u,ll v)
{
	e[t].v=v;
	e[t].next=p[u];
	p[u]=t++;
}
void dfs1(ll u)//树链剖分 
{  
    size[u]=1;
    for(int i=p[u];i!=-1;i=e[i].next)  
    {
        int v=e[i].v;  
        if(fa[u]!=v)
        {
            fa[v]=u;  
            d[v]=d[u]+1;  
            dfs1(v);
            size[u]+=size[v];  
            if(size[son[u]]<size[v])
                son[u]=v;
        }  
    }  
}
ll id[maxn],weight[maxn],top[maxn];
ll times=0;
void dfs2(ll u,ll topf)
{
	id[u]=++times;
	weight[times]=val[u];
	top[u]=topf;
	if(!son[u])
		return;
	dfs2(son[u],topf);
	for(ll i=p[u];i!=-1;i=e[i].next)
	{
		ll v=e[i].v;
		if(v!=fa[u] && v!=son[u])
			dfs2(v,v);
	}
}
ll tree[4*maxn][2];
void pushup(ll pos)//线段树部分
{
	tree[pos][0]=max(tree[pos<<1][0],tree[pos<<1|1][0]);
	tree[pos][1]=tree[pos<<1][1]+tree[pos<<1|1][1];
}
void build(ll pos,ll l,ll r)
{
	if(l==r)
	{
		tree[pos][0]=tree[pos][1]=weight[l];
		return;
	}
	ll mid=(l+r)>>1;
	build(pos<<1,l,mid);
	build(pos<<1|1,mid+1,r);
	pushup(pos);
}
void change(ll pos,ll l,ll r,ll aim,ll num)
{
	if(l==r)
	{
		tree[pos][0]=tree[pos][1]=num;
		return;
	}
	ll mid=(l+r)>>1;
	if(aim<=mid)
		change(pos<<1,l,mid,aim,num);
	else
		change(pos<<1|1,mid+1,r,aim,num);
	pushup(pos);
}
ll query(ll pos,ll l,ll r,ll s,ll e,ll flag)
{
	if(s<=l && r<=e)
		return tree[pos][flag];
	ll mid=(l+r)>>1,t1,t2;
	if(!flag)
		t1=t2=-inf;
	else
		t1=t2=0;
	if(s<=mid)
		t1=query(pos<<1,l,mid,s,e,flag);
	if(e>mid)
		t2=query(pos<<1|1,mid+1,r,s,e,flag);
	if(!flag)
		return max(t1,t2);
	return t1+t2;
}
int main()
{
	memset(p,-1,sizeof(p));
	scanf("%lld",&n);
	for(ll i=1;i<=n-1;i++)
	{
		ll u,v;
		scanf("%lld%lld",&u,&v);
		insert(u,v);
		insert(v,u);
	}
	for(ll i=1;i<=n;i++)
		scanf("%lld",&val[i]);
	dfs1(1);
	dfs2(1,1);
	build(1,1,times);
	ll q;
	scanf("%lld",&q);
	for(ll i=1;i<=q;i++)
	{
		char str[10];
		ll u,v;
		scanf("%s%lld%lld",str,&u,&v);
		if(strcmp(str,"CHANGE")==0)
			change(1,1,times,id[u],v);
		else if(strcmp(str,"QMAX")==0)
		{
			ll ans=-inf;
			while(top[u]!=top[v])
    		{
        		if(d[top[u]]<d[top[v]])
            		swap(u,v);
        		ll res=query(1,1,times,id[top[u]],id[u],0);
				u=fa[top[u]];
				ans=max(ans,res);
    		}
    		if(d[u]>d[v])
            	swap(u,v);
        	ll res=query(1,1,times,id[u],id[v],0);
			ans=max(ans,res);
    		printf("%lld\n",ans);
		}
		else
		{
			ll ans=0;
			while(top[u]!=top[v])
    		{
        		if(d[top[u]]<d[top[v]])
            		swap(u,v);
        		ll res=query(1,1,times,id[top[u]],id[u],1);
				u=fa[top[u]];
				ans+=res;
    		}
    		if(d[u]>d[v])
            	swap(u,v);
        	ll res=query(1,1,times,id[u],id[v],1);
			ans+=res;
    		printf("%lld\n",ans);
		}
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值