HAOI2015 树上操作 DFS序+线段树

题目大意:给定一棵树,每个点有点权。有m个操作:

  • 操作 1 :把某个节点 x 的点权增加 a 。
  • 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
  • 操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

定睛一看,这不就是树链剖分的模板题吗?!连问题都是一样的。。。(树链剖分模板)不过因为我写的不熟,只能大力线段树。。。

思路:首先把树的DFS序求出来,这样就把树上的操作转化为区间操作,再记录一下进栈出栈顺序。随后构建线段树,每段区间记录进栈点和出栈点个数。
操作1:进栈点,出栈点修改。
操作2:找到这个点的进栈,出栈,再区间加。
操作3:直接求根到进栈点的前缀和。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 100001;

int w[MAXN];
int Begin[MAXN], End[MAXN];
int fir[MAXN], nxt[MAXN << 1], to[MAXN << 1], cnt;
int a[MAXN << 1], cur, flag[MAXN << 1];

struct node{
	int l, r;
	int num[2];//1表示入栈,0表示出栈 
	long long data, tag; 
}t[MAXN << 3];

inline int read(){
	int k = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
	return k * f;
}

inline void add_edge(int a, int b){
	to[cnt] = b;
	nxt[cnt] = fir[a];
	fir[a] = cnt++;
}

inline void pushup(int u){
	t[u].data = t[u << 1].data + t[u << 1 | 1].data;
}

inline void add(int u, long long k){
	t[u].tag += k;
	t[u].data += (long long)(t[u].num[1] - t[u].num[0]) * k; //进栈点个数减出栈点个数
}

inline void pushdown(int u){
	if(t[u].tag){
		add(u << 1, t[u].tag);
		add(u << 1 | 1, t[u].tag);
		t[u].tag = 0;
	}
}

void build(int u, int L, int R){
	t[u].l = L, t[u].r = R;
	if(L == R){
		t[u].num[flag[L]]++;
		t[u].data = flag[L] ? w[a[L]] : -w[a[L]];
		return;
	}
	int mid = (L + R) >> 1;
	build(u << 1, L, mid);
	build(u << 1 | 1, mid + 1, R);
	t[u].num[0] = t[u << 1].num[0] + t[u << 1 | 1].num[0];
	t[u].num[1] = t[u << 1].num[1] + t[u << 1 | 1].num[1];
	pushup(u);
}

void change(int u, int L, int R, long long k){
	if(t[u].l >= L && t[u].r <= R){
		add(u, k);
		return;
	}
	pushdown(u);
	if(t[u << 1].r >= L) change(u << 1, L, R, k);
	if(t[u << 1 | 1].l <= R) change(u << 1 | 1, L, R, k);
	pushup(u);
}

long long query(int u, int L, int R){
	if(t[u].l >= L && t[u].r <= R)
		return t[u].data;
	pushdown(u);
	long long ans = 0;
	if(t[u << 1].r >= L) ans += query(u << 1, L, R);
	if(t[u << 1 | 1].l <= R) ans += query(u << 1 | 1, L, R);
	return ans;
}

void dfs(int u, int fa){
	a[++cur] = u;
	flag[cur] = 1;
	Begin[u] = cur;
	for(int i = fir[u]; i != -1; i = nxt[i]){
		int v = to[i];
		if(v == fa) continue;
		dfs(v, u);
	}
	a[++cur] = u;
	flag[cur] = 0;
	End[u] = cur;
}


int main(){
	memset(fir, -1, sizeof(fir));
	int n = read(), m = read();
	for(int i = 1; i <= n; i++)
		w[i] = read();
	for(int i = 1; i < n; i++){
		int a = read(), b = read();
		add_edge(a, b);
		add_edge(b, a);
	}
	
	dfs(1, 0);
	build(1, 1, cur);
	
	for(int i = 1; i <= m; i++){
		int opt = read(), x = read(), k;
		switch(opt){
			case 1:
				k = read();
				change(1, Begin[x], Begin[x], k);
				change(1, End[x], End[x], k);
				break;
			case 2:
				k = read();
				change(1, Begin[x], End[x], k);
				break;
			case 3:
				printf("%lld\n", query(1, 1, Begin[x]));
				break;
		}
	}
	
	return 0;
}

鸽了几天了,咕咕咕~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值