【luogu P4183】Cow at Large P(点分治)(图论)(树状数组)

Cow at Large P

题目链接:luogu P4183

题目大意

给你一棵树,然后叶子节点可以放守卫。
然后有个人在树上,然后每个时刻那个人和守卫都可以移动,如果人和守卫相遇人就被抓了,如果人走到叶子节点他就逃走了。
然后问你对于每个树上的位置,如果一开始那个人在这里,至少要多少个守卫才能抓住他。

思路

首先不难看到一个简单的事情就是因为是树,所以如果我们把那个人一开始的位置当做根。
守卫就会往上走,时间越长就会断掉那个人越多的路。
所以我们在一个子树内就只需要选一个守卫,那我们具体看看那个子树顶端的点的位置。

然后你会发现假如守不住,那个人往这走,设那个顶端点为 x x x,距离最近的叶子节点是 y y y(因为守卫肯定是放这),一开始人在 z z z,那就是 d i s ( x , z ) ⩾ d i s ( x , y ) dis(x,z)\geqslant dis(x,y) dis(x,z)dis(x,y) 而且 d i s ( f a x , z ) < d i s ( x , y ) dis(fa_x,z)<dis(x,y) dis(fax,z)<dis(x,y)
那这个临界点也不好搞,考虑弄的普遍一点,就先只看左边的式子满足的条件。

会发现满足的是这个顶点以及它的子树,那你能不能想个办法让这整个子树只贡献一次。
然后就是图论中的点边关系,在 n n n 个点的子树上,有:
∑ n d u i = 2 n − 1 \sum\limits^ndu_i=2n-1 ndui=2n1
(因为子树顶端的点连一个父亲边)
1 = 2 n − ∑ n d u i = ∑ n ( 2 − d u i ) 1=2n-\sum\limits^ndu_i=\sum\limits^n(2-du_i) 1=2nndui=n(2dui)

所以答案可以变成这个:
a n s x = ∑ i = 1 n [ d i s ( x , i ) ⩾ g i ] ( 2 − d u i ) ans_x=\sum\limits_{i=1}^n[dis(x,i)\geqslant g_i](2-du_i) ansx=i=1n[dis(x,i)gi](2dui)
然后这个是点对问题可以用点分治,然后用个树状数组来存桶就可以搞了。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N = 7e4 + 100;
int n, sz[N], root, max_root, du[N], g[N], dis[N], ans[N];
vector <int> G[N];
bool in[N];

struct SZSJ {
	int f[N << 1], n;
	
	void clear(int m) {
		n = m; for (int i = 1; i <= n; i++) f[i] = 0;
	}
	
	void add(int x, int y) {
		for (; x <= n; x += x & (-x))
			f[x] += y;
	}
	
	int query(int x) {
		int re = 0;
		for (; x; x -= x & (-x))
			re += f[x];
		return re;
	}
}T;

void Init() {
	queue <int> q;
	for (int i = 1; i <= n; i++) if (du[i] == 1) q.push(i), in[i] = 1;
	while (!q.empty()) {
		int now = q.front(); q.pop();
		for (int i = 0; i < G[now].size(); i++) { int x = G[now][i];
			if (in[x]) continue;
			g[x] = g[now] + 1; in[x] = 1; q.push(x);
		}
	}
	memset(in, 0, sizeof(in));
	dis[0] = -1;
}

void dfs0(int now, int father) {
	sz[now] = 1; dis[now] = dis[father] + 1;
	for (int i = 0; i < G[now].size(); i++) { int x = G[now][i];
		if (x == father || in[x]) continue;
		dfs0(x, now); sz[now] += sz[x];
	}
}

void get_root(int now, int father, int sum) {
	int maxn = sum - sz[now];
	for (int i = 0; i < G[now].size(); i++) { int x = G[now][i];
		if (x == father || in[x]) continue;
		get_root(x, now, sum);
		maxn = max(maxn, sz[x]);
	}
	if (maxn < max_root) max_root = maxn, root = now;
}

int dadi;

void dfs1(int now, int father) {
	ans[now] += T.query(dis[now] + dadi);
	for (int i = 0; i < G[now].size(); i++) { int x = G[now][i];
		if (x == father || in[x]) continue;
		dfs1(x, now);
	}
}

void dfs2(int now, int father) {
	T.add(g[now] - dis[now] + dadi, 2 - du[now]);//加sz[now]防止负号 
	for (int i = 0; i < G[now].size(); i++) { int x = G[now][i];
		if (x == father || in[x]) continue;
		dfs2(x, now);
	}
}

void clac(int now) {
	dadi = sz[now]; 
	T.clear(sz[now] * 2);
	for (int i = 0; i < G[now].size(); i++) { int x = G[now][i];
		if (in[x]) continue;
		dfs1(x, now);
		dfs2(x, now);
	}
	T.clear(sz[now] * 2);
	T.add(g[now] + dadi, 2 - du[now]);
	for (int i = G[now].size() - 1; i >= 0; i--) { int x = G[now][i];
		if (in[x]) continue;
		dfs1(x, now);
		dfs2(x, now);
	}
	ans[now] += T.query(dadi);
}

void work(int now) {
	in[now] = 1;
	dfs0(now, 0);
	clac(now);
	for (int i = 0; i < G[now].size(); i++) { int x = G[now][i];
		if (in[x]) continue;
		max_root = 2e9; get_root(x, now, sz[x]);
		work(root);
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		int x, y; scanf("%d %d", &x, &y);
		G[x].push_back(y); G[y].push_back(x);
		du[x]++; du[y]++;
	}
	
	Init();
	
	dfs0(1, 0);
	max_root = 2e9; get_root(1, 0, n);
	work(root);
	
	for (int i = 1; i <= n; i++)
		if (du[i] != 1) printf("%d\n", ans[i]);
			else printf("1\n");
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值