【ybt金牌导航5-1-2】【luogu P2146】软件管理 / 软件包管理器

本文详细解析了洛谷P2146题目,主要涉及树链剖分和线段树的数据结构。通过树链剖分实现树的重儿子先走的DFS序,利用线段树处理点权的修改,包括链上点权全变为1和子树点权全变为0的操作。文章提供了完整的C++代码实现,并解释了如何通过线段树进行查询和修改操作,有效地计算每次操作中点权改变的点的数量。
摘要由CSDN通过智能技术生成

软件管理 / 软件包管理器

题目链接:ybt金牌导航5-1-2 / luogu P2146

题目大意

有一个树,你每次会操作,把一条到根节点的链上点权都改成一,或把一个点与其子树的点权都改成 0。
问你每次操作树上点权改变的点的个数。

思路

这道题看到修改链上点权,不难想到可以用树链剖分。
但是它还有修改子树啊。

但你会发现,根据重儿子先走的 dfs 序,一个子树上的点的 dfs 序还是连续的。(毕竟它还是 dfs)
那就是说你还是可以用线段树来搞,那位置就是 p l x ∼ p l x + s z x − 1 pl_x\sim pl_x+sz_x-1 plxplx+szx1
p l x pl_x plx x x x 点在线段树中的位置, s z x sz_x szx x x x 所在子树的大小)

然后每次就查询,然后修改就好了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

struct node {
	int to, nxt;
}e[100001];
int n, fa[100001], le[100001], KK, tmp, Q, x;
int deg[100001], sz[100001], son[100001];
int top[100001], pl[100001], dfn[100001];
int sum[400001], lazy[400001];
char op;

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
}

void dfs1(int now, int father) {//树链剖分两次 dfs
	deg[now] = deg[father] + 1;
	sz[now] = 1;
	int maxn = 0;
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfs1(e[i].to, now);
			sz[now] += sz[e[i].to];
			if (sz[e[i].to] > maxn) {
				maxn = sz[e[i].to];
				son[now] = e[i].to;
			}
		}
}

void dfs2(int now, int father) {
	if (son[now]) {
		pl[son[now]] = ++tmp;
		top[son[now]] = top[now];
		dfn[tmp] = son[now];
		dfs2(son[now], now);
	}
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father && e[i].to != son[now]) {
			pl[e[i].to] = ++tmp;
			top[e[i].to] = e[i].to;
			dfn[tmp] = e[i].to;
			dfs2(e[i].to, now);
		}
}

void down(int now, int l, int r) {//线段树操作
	if (lazy[now] != -1 && l != r) {
		int mid = (l + r) >> 1;
		sum[now << 1] = lazy[now] * (mid - l + 1);
		sum[now << 1 | 1] = lazy[now] * (r - (mid + 1) + 1);
		lazy[now << 1] = lazy[now << 1 | 1] = lazy[now];
		lazy[now] = -1;
	}
}

void up(int now) {
	sum[now] = sum[now << 1] + sum[now << 1 | 1];
}

int query(int now, int l, int r, int L, int R) {//此查询是查询 1 的个数
	if (L <= l && r <= R) return sum[now];
	
	down(now, l, r);
	int mid = (l + r) >> 1, re = 0;
	if (L <= mid) re += query(now << 1, l, mid, L, R);
	if (mid < R) re += query(now << 1 | 1, mid + 1, r, L, R);
	
	return re;
}

void change(int now, int l, int r, int L, int R, int num) {
	if (L <= l && r <= R) {
		lazy[now] = num;
		sum[now] = num * (r - l + 1);
		return ;
	}
	
	down(now, l, r);
	int mid = (l + r) >> 1;
	if (L <= mid) change(now << 1, l, mid, L, R, num);
	if (mid < R) change(now << 1 | 1, mid + 1, r, L, R, num);
	
	up(now);
}

//注意修改成一的是要查询 0 的个数,那 0 的个数就是所有的个数 - 1 的个数
int ask_and_make(int x, int y) {
	int X = top[x], Y = top[y], re = 0;
	while (X != Y) {
		if (deg[X] < deg[Y]) {
			swap(X, Y);
			swap(x, y);
		}
		re += (deg[x] - deg[X] + 1) - query(1, 1, tmp, pl[X], pl[x]);
		change(1, 1, tmp, pl[X], pl[x], 1);
		x = fa[X];
		X = top[x];
	}
	
	if (deg[x] > deg[y]) swap(x, y);
	re += (deg[y] - deg[x] + 1) - query(1, 1, tmp, pl[x], pl[y]);
	change(1, 1, tmp, pl[x], pl[y], 1);
	
	return re;
}

int ask_and_clear(int x) {
	int re = query(1, 1, tmp, pl[x], pl[x] + sz[x] - 1);
	change(1, 1, tmp, pl[x], pl[x] + sz[x] - 1, 0);
	return re;
}

int main() {
	memset(lazy, -1, sizeof(lazy));
	
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		scanf("%d", &fa[i]);
		add(fa[i], i);
	}
	
	dfs1(0, 0);
	pl[0] = ++tmp; top[0] = 0; dfn[1] = 0;
	dfs2(0, 0);
	
	scanf("%d", &Q);
	while (Q--) {
		op = getchar();
		while (op != 'i' && op != 'u') op = getchar();
		if (op == 'i') {
			for (int i = 1; i <= 6; i++) getchar();
			scanf("%d", &x);
			
			printf("%d\n", ask_and_make(0, x));
			
			continue;
		}
		if (op == 'u') {
			for (int i = 1; i <= 8; i++) getchar();
			scanf("%d", &x);
			
			printf("%d\n", ask_and_clear(x));
			
			continue;
		}
	}
	
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值