1036: [ZJOI2008]树的统计Count 树链剖分裸题

题目链接:点击打开链接

树链剖分裸题:

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stdlib.h>
#define N 60003
#define L(x) (x<<1)
#define R(x) (x<<1|1)
#define Mid(x,y) ((x+y)>>1)
#define inf 10000000
using namespace std;

struct Edge{
	int to, nex;
}edge[N*2];
int head[N], edgenum;
void add(int u, int v){
	Edge E={v,head[u]}; 	edge[edgenum] = E; 	head[u] = edgenum++;
}

int n, Query, a[N];

int fa[N];//父节点
int dep[N];//深度
int son[N];//重儿子
int size[N];//子树节点数
int p[N]; //p[v]表示v在线段树中的位置
int fp[N]; //与p数组相反
int top[N]; // top[v] 表示v所在的重链的顶端节点 (这样 v与top[v] 的重路径可以直接在线段树上操作)lkjiyhtrf saQ

int dfs(int u, int Father, int deep){
	fa[u] = Father;		size[u] = 1;	dep[u] = deep;
	for(int i = head[u]; ~i; i = edge[i].nex){
		int v = edge[i].to;
		if(v == Father)continue;
		size[u] += dfs(v, u, deep+1);
		if(son[u] == -1 || size[v] > size[son[u]])son[u] = v;
	}
	return size[u];
}

int tree_id; //tree_id表示线段树中所有的边(给每条边编号)
void Have_p(int u, int Father){
	top[u] = Father;
	p[u] = tree_id++; fp[p[u]] = u;
	if(son[u] == -1)return ;
	Have_p(son[u], top[u]); //注意这里
	for(int i = head[u]; ~i; i = edge[i].nex){
		int v = edge[i].to;
		if(v != son[u] && v != fa[u])Have_p(v, v); //注意这里
	}
}

struct node{
	int l, r;
	int Max, Sum;
}tree[N*8];
void push_up(int id){
	tree[id].Max = max(tree[L(id)].Max, tree[R(id)].Max);
	tree[id].Sum = tree[L(id)].Sum + tree[R(id)].Sum;
}
void build(int l, int r, int id){
	tree[id].l = l, tree[id].r = r;
	if(l == r)
	{
		tree[id].Sum = tree[id].Max = a[fp[l]]; //注意这里
		return ;
	}
	int mid = Mid(l, r);
	build(l, mid, L(id));
	build(mid+1, r, R(id));
	push_up(id);
}

void updata(int pos, int val, int id){
	if(tree[id].l == tree[id].r)
	{
		tree[id].Max = tree[id].Sum = val;
		return ;
	}
	int mid = Mid(tree[id].l, tree[id].r);
	if(pos <= mid)updata(pos, val, L(id));
	else updata(pos, val, R(id));
	push_up(id);
}
int querySum(int l, int r, int id){
	if(l == tree[id].l && tree[id].r == r)return tree[id].Sum;
	int mid = Mid(tree[id].l, tree[id].r);
	if(r<=mid) return querySum(l, r, L(id));
	else if(mid<l)return querySum(l, r, R(id));
	return querySum(l, mid, L(id)) + querySum(mid+1, r, R(id));
}
int queryMax(int l, int r, int id){
	if(l == tree[id].l && tree[id].r == r)return tree[id].Max;
	int mid = Mid(tree[id].l, tree[id].r);
	if(r<=mid) return queryMax(l, r, L(id));
	else if(mid<l)return queryMax(l, r, R(id));
	return max(queryMax(l, mid, L(id)), queryMax(mid+1, r, R(id)));
}
int findSum(int u, int v){
	int f1 = top[u], f2 = top[v];
	int tmp = 0;
	while(f1 != f2)//相等就说明两点在同一重路径上 || 相同点
	{
		if(dep[f1]<dep[f2]) swap(f1, f2), swap(u, v); //注意这里
		tmp += querySum(p[f1], p[u], 1); //每次只爬其中一条重链!
		u = fa[f1]; //注意这里
		f1 = top[u];
	}
	if(dep[u] > dep[v])swap(u, v); //注意这里
	return tmp + querySum(p[u], p[v], 1);
}
int findMax(int u, int v){
	int f1 = top[u], f2 = top[v];
	int tmp = -inf;
	while(f1 != f2)
	{
		if(dep[f1]<dep[f2]) swap(f1, f2), swap(u, v);
		tmp = max(tmp, queryMax(p[f1], p[u], 1));
		u = fa[f1];
		f1 = top[u];
	}
	if(dep[u] > dep[v])swap(u, v);
	return max(tmp, queryMax(p[u], p[v], 1));
}

void init(){
	memset(head, -1, sizeof(head));
	memset(son, -1, sizeof(son));  //注意这里son初始化
	edgenum = 0;
	tree_id = 1; //这个的所有可用编号就是线段树的区间 注意这里是从1开始的
}
char op[10];
int main(){
	int u, v, i;
	while(~scanf("%d",&n)){
		init();
		for(i = 0; i < n-1; i++){scanf("%d %d",&u,&v);	add(u,v), add(v,u);	}
		for(i = 1; i <= n; i++)scanf("%d",&a[i]);
		dfs(1, 1, 0);
		Have_p(1, 1);
		build(1, n, 1);
		scanf("%d",&Query);
		while(Query--){
			scanf("%s%d%d",op,&u,&v);
			if(op[0] == 'C')
				updata(p[u],v,1); //注意这里
			else if(op[1] == 'M')
				printf("%d\n", findMax(u, v));
			else 
				printf("%d\n", findSum(u, v));
		}
	}
	return 0;
}

/*
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

1
5
QMAX 1 1
QSUM 1 1
CHANGE 1 4 
QMAX 1 1
QSUM 1 1


*/


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值