Codeforces Round #381 (Div. 1) B. Alyona and a tree 二分 + 前缀和

题目链接

一、题意

有一棵树,1为根节点。树的节点i有一个权值a[i],代表节点i的祖先节点x如果到i的路经长度小于等于a[i],则称x控制i。控制关系不传递,即x控制j,j控制i并不一定能得到x控制i。

现在给出树的边、每一个节点的权值,求每一个节点能够控制的节点的数目。

二、思路

思考每一条链。可以发现链底端的节点v可以被链上连续的节点u控制。如果用depth[]数组表示从根节点到每个节点的距离,我们可以得到u可以被v控制,当且仅当满足条件:

depth[u] - depth[v] <= a[u]

移项可得条件为:

depth[u] - a[u] <= depth[v]

现在我们再回来看这条底端为节点u的链,可以看到确实所有满足depth[v] >= 某个值的节点v都控制了u,而这些节点显然是连续的。所以我们其实是要给链上的一个区间全部 + 1。可以用前缀和差分的方法,找到 “从根节点向下最后一个不能控制u的节点”,将它的答案 - 1,并将u的父节点答案 + 1,这样就可以在从下而上回退搜索路径的时候进行相加得到结果。“将u的父节点答案 + 1“我用的处理是将u答案加1,在父节点对u进行加法操作的时候将u的答案再回 - 1。这里还要注意如果一个节点都不能控制u的时候,是不执行“找到 ‘从根节点向下最后一个不能控制u的节点’,将它的答案 - 1”操作的,可见22行的判断语句。

三、代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int max_n = 2e5 + 10;
int n;
ll a[max_n];
vector<pair<int, ll> > G[max_n];
vector<pair<ll, int> > path; //第一个值是深度,第二个是节点编号,要依照深度大小来进行二分
int ans[max_n];
ll depth[max_n];

void dfs(int v) {
	// printf("dfs%d:\n", v);
	path.push_back(make_pair(depth[v], v));
	if(v != 1)
		ans[v] = 1;
	else ans[v] = 0;
	ll deepre = depth[v] - a[v];
	int p = lower_bound(path.begin(), path.end(), make_pair(deepre, -1)) - path.begin() - 1;
	//p是最后一个控制不到v的path里的编号,不是元素编号本身!
	if (p >= 0) {
		int ver = path[p].second;
		ans[ver]--;
		// printf("ans[%d]--\n\n", p);
	}
	//完成前缀和的记录,将第一个控制不到v的点数量-1,就实现了将控制得到v的点的区间+1的操作
	for (int i = 0; i < G[v].size(); i++) {
		pair<int, ll> e;
		e = G[v][i];
		depth[e.first] = depth[v] + e.second;
		dfs(e.first);
		ans[v] += ans[e.first];
		ans[e.first]--;
		path.pop_back();
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%I64d", &a[i]);
	}
	for (int to = 2; to <= n; to++) {
		int from;
		ll w;
		scanf("%d %I64d", &from, &w);
		G[from].push_back(make_pair(to, w));
	}

	dfs(1);

	for (int i = 1; i <= n; i++) {
		printf("%d", ans[i]);
		if (i == n)
			printf("\n");
		else printf(" ");
	}
	return 0;
}

四、总结

顺手复习了差分。把搜索和差分放在一起就做不出来了(其实单独放差分出来我也做不出来),的确是菜,要坚持A题。

猜你喜欢

转载自blog.csdn.net/xuzonghao/article/details/94621763