CF383C Propagating tree

知识点:线段树,DFS序,前缀和

这个题用树状数组应该也能做,但是因为是在线段树题单里面的,所以第一之间就用线段树做了,这个题需要用到一个小的知识点,就是DFS序,这里我们求的是树的DFS序,别的结构至于图有没有DFS序之类的东西我不知道,DFS序其实就和深搜的访问时机关系密切,对于一个结点,我们刚进入的时候,以及要离开的时候,也就是这个结点所有的子节点都访问完的时候,这两个时间结点,我们把当前的结点的序号加入到DFS序里面,所以DFS序是结点的两倍,是结点的访问顺序,然后主要的用途,这里做了两个题了,主要的用途就是我们修改以某个结点为根节点的子树的时候用的上的,对于某个结点,我们记录他两次出现的位置,那么中间夹着的就是以他为根节点的子树的DFS序,刚才翻书看了一下,树才有DFS序,把树上的整个子树的修改转化为序列上的问题

然后这个题还有一个点,那么就是某个结点修改,那么剩下的结点都是逐层相反的修改,这里我们采用的是前缀和的方法来解决,首先我们求DFS序的时候,记录每层每个位置的数值,不是1就是-1,人为规定第一层是1,那么第二层就是-1,以此类推,这样得出DFS序之后,就得出了与DFS相对应的一个1 -1序列,然后对这个序列求前缀和,我们要对DFS序对应的序列进行区间修改,实际上就是题目输入数值乘以这个区间对应的1 -1序列的区间和,但是需要注意是,我们要修改某个位置,但是那个位置对用的是-1,那么题目里面输入是数值要乘以-1,要不然显然是错误的,这样这个题就在nlogn的时间内完成了,还没有思考树状数组的做法,线段树的思维含量比较低20分钟就写完了

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 5;

struct segt {
	int l, r, sum, add;
} t[N * 8];

int n, m, a[N], b[N * 2], L[N], R[N], cnt, c[N * 2];
int tot, ver[N * 2], head[N], nxt[N * 2];

void add(int x, int y) {
	ver[++tot] = y;
	nxt[tot] = head[x]; head[x] = tot;
}

void dfs(int v, int fa, int x) {
	b[++cnt] = v;
	c[cnt] = x;
	L[v] = cnt;
	for (int i = head[v]; i; i = nxt[i]) {
		if (ver[i] != fa) dfs(ver[i], v, -x);
	}
	b[++cnt] = v;
	c[cnt] = x;
	R[v] = cnt;
}

void pushdown(int p) {
	int p1 = p * 2, p2 = p * 2 + 1;
	t[p1].sum += t[p].add * (c[t[p1].r] - c[t[p1].l - 1]);
	t[p2].sum += t[p].add * (c[t[p2].r] - c[t[p2].l - 1]);
	t[p1].add += t[p].add;
	t[p2].add += t[p].add;
	t[p].add = 0;
}

void build(int p, int l, int r) {
	t[p].l = l; t[p].r = r;
	if (l == r) { t[p].sum = a[b[l]]; return; }
	int mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
	t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
}

void change(int p, int l, int r, int val) {
	if (l <= t[p].l && t[p].r <= r) {
		t[p].sum += val * (c[t[p].r] - c[t[p].l - 1]);
		t[p].add += val;
		return;
	}
	pushdown(p);
	int mid = (t[p].l + t[p].r) / 2;
	if (l <= mid) change(p * 2, l, r, val);
	if (r > mid) change(p * 2 + 1, l, r, val);
	t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
}

int ask(int p, int x) {
	if (t[p].l == t[p].r) return t[p].sum;
	pushdown(p);
	int mid = (t[p].l + t[p].r) / 2;
	if (x <= mid) return ask(p * 2, x);
	else return ask(p * 2 + 1, x);
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	for (int i = 1; i < n; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y); add(y, x);
	}
	dfs(1, 0, 1);
	build(1, 1, n * 2);
	for (int i = 1; i <= n * 2; i++) {
		c[i] += c[i - 1];
	}
	while (m--) {
		int op, x, val;
		scanf("%d%d", &op, &x);
		if (op == 1) {
			scanf("%d", &val);
			if (c[L[x]] - c[L[x] - 1] == -1) val *= -1;
			change(1, L[x], R[x], val);
		} else {
			printf("%d\n", ask(1, L[x]));
		}
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值