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]);
}
转载注意标注出处:
转自Cold_Chair的博客+原博客地址