USACO 2018 January Contest, Platinum Problem 2. Cow at Large JZOJ 100130. 鱼池逃脱(贪心+树上数数转换+点分治)

题目:

http://usaco.org/index.php?page=viewproblem2&cpid=793

https://gmoj.net/senior/#main/show/100130

题解:

考虑定根时怎么做?

假设在一个叶子y放了个人,那么这个人会一直往根的方向走,若根的人往y这个方向走,它俩在中点相遇,也就是根的人不可能从中点的子树走出去。

贪心的想,每次选深度最小的叶子,然后把中点的子树覆盖了,继续选没有被覆盖的叶子。

或者说,给每个叶子到根的中点打上标记,然后从根往下遍历,看看遇到多少个标记点。

得到了\(O(n^2)\)的做法。

对每个根,我们只想知道有多少个标记点,所以需要找到一种巧妙的统计方法。

注意这些标记点之间一定不成祖先关系。

对于一个\(k\)个点的子树,度数和\(=2*k-1\),这样的话\(=\sum_{x在子树里}2-r[i]=1\),恰好可以统计这个子树一次。

这些标记点之间不成祖先关系,那么统计它们子树并的\(\sum2-r[i]\)就好了。

\(f[x]\)\(x\)到最近叶子的距离,这个可以通过多源BFS或树形dp求出。

\(y\)\(root\)时,\(x\)会被作为子树并的点统计到,当且仅当\(dis(x,y)>=f[x]\)

那么就可以点分治了:

点分治后,相当于统计\(dep[x]+dep[y]>=f[x]\)

\(dep[y]>=f[x]-dep[x]\)

\(f[x]-dep[x]\)可能很大,但是\(dep[y]\)一定在分治子树大小内,所以可以记一个分治子树大小的桶,再搞个前缀和来查询,还需要容斥减掉来自同一个分治子树的答案。

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int N = 70005;

int n, x, y, r[N];
int fi[N], to[N * 2], nt[N * 2], tot;

void link(int x, int y) {
	nt[++ tot] = fi[x], to[tot] = y, fi[x] = tot;
}

int d[N], d0, bz[N], f[N];

void bfs() {
	fo(i, 1, n) if(r[i] == 1)
		d[++ d0] = i, bz[i] = 1;
	for(int i = 1; i <= d0; i ++) {
		int x = d[i];
		for(int j = fi[x]; j; j = nt[j]) {
			int y = to[j];
			if(!bz[y]) bz[y] = 1, f[y] = f[x] + 1, d[++ d0] = y;
		}
	}
}

int siz[N], mx[N], G;

void fg(int x) {
	bz[x] = 1;
	siz[x] = 1; mx[x] = 0;
	for(int i = fi[x]; i; i = nt[i]) if(!bz[to[i]])
		fg(to[i]), siz[x] += siz[to[i]], mx[x] = max(mx[x], siz[to[i]]);
	mx[x] = max(mx[x], siz[0] - siz[x]);
	if(mx[x] < mx[G]) G = x;
	bz[x] = 0;
}

int dep[N];

void dfs(int x) {
	bz[x] = 1;
	d[++ d0] = x;
	for(int i = fi[x]; i; i = nt[i]) if(!bz[to[i]]) {
		dep[to[i]] = dep[x] + 1;
		dfs(to[i]);
	}
	bz[x] = 0;
}

ll cnt[N], ans[N];

void calc(int m, int xs) {
	fo(i, 0, m) cnt[i] = 0;
	fo(i, 1, d0) {
		int v = f[d[i]] - dep[d[i]];
		if(v <= m) cnt[max(0, v)] += 2 - r[d[i]];
	}
	fo(i, 1, m) cnt[i] += cnt[i - 1];
	fo(i, 1, d0) ans[d[i]] += xs * (cnt[dep[d[i]]]);
}

void dg(int x) {
	fg(x);
	d0 = 0; dep[x] = 0; dfs(x);
	calc(siz[x], 1);
	bz[x] = 1;
	for(int i = fi[x]; i; i = nt[i]) if(!bz[to[i]]) {
		int y = to[i];
		d0 = 0; dep[y] = 1; dfs(y);
		calc(siz[y], -1);
	}
	for(int i = fi[x]; i; i = nt[i]) if(!bz[to[i]])
		siz[0] = siz[to[i]], G = 0, fg(to[i]), dg(G);
}

int main() {
	freopen("atlarge.in", "r", stdin);
	freopen("atlarge.out", "w", stdout);
	scanf("%d", &n);
	if(n == 1) {
		pp("1\n"); return 0;
	}
	fo(i, 1, n - 1) {
		scanf("%d %d", &x, &y);
		link(x, y); link(y, x);
		r[x] ++; r[y] ++;
	}
	bfs();
	fo(i, 1, n) bz[i] = 0;
	siz[0] = mx[0] = n, fg(1), dg(G);
	fo(i, 1, n) pp("%lld\n", r[i] == 1 ? 1 : ans[i]);
}

posted @ 2020-02-24 18:42  Cold_Chair  阅读(266)  评论(0编辑  收藏  举报