洛谷 P2590 [ZJOI2008]树的统计

 PS:如果读过题了可以跳过题目描述直接到题解部分

提交链接:洛谷 P2590 [ZJOI2008]树的统计

题目

题目描述

一棵树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。

我们将以下面的形式来要求你对这棵树完成一些操作:

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

注意:从点 u 到点 v 的路径上的节点包括 u 和 v 本身。

输入格式

输入文件的第一行为一个整数 n,表示节点的个数。

接下来 n−1 行,每行 2 个整数 a 和 b,表示节点 a 和节点 b 之间有一条边相连。

接下来一行 n 个整数,第 i 个整数 w[i​] 表示节点 i 的权值。

接下来 1 行,为一个整数 q,表示操作的总数。

接下来 q 行,每行一个操作,以 CHANGE u t 或者 QMAX u v 或者 QSUM u v 的形式给出。

输出格式

对于每个 QMAX 或者 QSUM 的操作,每行输出一个整数表示要求输出的结果。

样例

输入

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

输出

4
1
2
2
10
6
5
6
5
16

说明/提示

对于 100% 的数据,保证 1≤n≤3×10^4,0≤q≤2×10^5。

中途操作中保证每个节点的权值 w 在 −3×10^4 到 3×10^4 之间。

题解

树链剖分

今天偷个懒,这部分就自行理解一下啦(主要是改代码改烦了

树链剖分-OI Wiki

其实有些东西自己看看,把代码敲完,把题改过的时候自然就懂了。

线段树

在这里我郑重地推荐一下zkw线段树,个人觉得超级好用,但我(懒得)不想写,这里附上我学习zkw的链接

zkw线段树

强烈推荐!强烈推荐!强烈推荐!强烈推荐!强烈推荐!强烈推荐!强烈推荐!强烈推荐!

代码实现

//洛谷 P2590 [ZJOI2008]树的统计
#include<iostream>
#include<cstdio>
using namespace std;
const int N=30010;
int n;
int u,v;
int q;
int fa[N];//父亲 
int dep[N];//深度 
int siz[N];//子树节点数 
int son[N];//重儿子 
int top[N];//重链的顶部节点 
int dfn[N];//节点的dfs序 
int rnk[N];//dfs序对应的节点 
int head[N];
int w[N];
int cnt;
int m;

struct edge{
	int v;
	int nex;
}a[N<<1];

struct node{
	int v,maxx;
}b[N<<2];

void build1(int u,int v){
	a[++cnt].v=v;
	a[cnt].nex=head[u];
	head[u]=cnt;
}

void dfs1(int u,int d){
	siz[u]=1;
	for(int i=head[u];i;i=a[i].nex){
		int v=a[i].v;
		if(!dep[v]){
			dep[v]=d+1;
			fa[v]=u;
			dfs1(v,d+1);
			siz[u]+=siz[v];
			if(!son[u]||siz[v]>siz[son[u]]){
				son[u]=v;
			}
		}
	}
}

void dfs2(int u,int t){
	top[u]=t;
	dfn[u]=++cnt;
	rnk[cnt]=u;
	if(!son[u]){
		return ;
	}
	dfs2(son[u],t);
	for(int i=head[u];i;i=a[i].nex){
		int v=a[i].v;
		if(v!=son[u]&&v!=fa[u]){
			dfs2(v,v);
		}
	}
}

void build2(){
	for(m=1;m<n;m<<=1);
	for(int i=m+1;i<=m+n;++i){
		b[i].maxx=b[i].v=w[rnk[i-m]];
	}
	for(int i=m-1;i;--i){
		b[i].maxx=max(b[i<<1].maxx,b[i<<1|1].maxx);
		b[i].v=b[i<<1].v+b[i<<1|1].v;
	}
}

int qsum(int s,int t){
	int ans=0;
	for(s=s+m-1,t=t+m+1;s^t^1;s>>=1,t>>=1){
		if(~s&1){
			ans+=b[s^1].v;
		}
		if(t&1){
			ans+=b[t^1].v;
		}
	}
	return ans;
}

int qmax(int s,int t){
	int ans=-30000010;
	for(s=s+m-1,t=t+m+1;s^t^1;s>>=1,t>>=1){
		if(~s&1){
			ans=max(ans,b[s^1].maxx);
		}
		if(t&1){
			ans=max(ans,b[t^1].maxx);
		}
	}
	return ans;
}

int qs(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]){
			swap(x,y);
		}
		ans+=qsum(dfn[top[x]],dfn[x]);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]){
		swap(x,y);
	}
	return ans+=qsum(dfn[x],dfn[y]);
}

int qm(int x,int y){
	int ans=-30000010;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]){
			swap(x,y);
		}
		ans=max(ans,qmax(dfn[top[x]],dfn[x]));
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]){
		swap(x,y);
	}
	return ans=max(ans,qmax(dfn[x],dfn[y]));
}

void change(int x,int v){
	x=m+dfn[x];
	b[x].maxx=b[x].v=v;
	while(x){
		x>>=1;
		b[x].v=b[x<<1].v+b[x<<1|1].v;
		b[x].maxx=max(b[x<<1].maxx,b[x<<1|1].maxx);
	}
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<n;++i){
		scanf("%d%d",&u,&v);
		build1(u,v);
		build1(v,u);
	}
	for(int i=1;i<=n;++i){
		scanf("%d",&w[i]);
	}
	dep[1]=1;
	dfs1(1,1);
	cnt=0;
	dfs2(1,1);
	build2();
	scanf("%d",&q);
	for(int i=1;i<=q;++i){
		char s[10];
		scanf("%s%d%d",&s,&u,&v);
		if(s[1]=='M'){
			printf("%d\n",qm(u,v));
		}
		else if(s[1]=='S'){
			printf("%d\n",qs(u,v));
		}
		else{
			change(u,v);
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月半流苏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值