P4183 [USACO18JAN]Cow at Large P
思路
我们不妨设出发点为根 \(rt\),并设 \(d[u]\) 表示 \(u\) 到最近的叶子结点的距离。
显然,如果一个点 \(u\) 满足:\(rt\) 到 \(u\) 的距离 \(\ge\) \(d[u]\),那么我们就可以放一个农民在那个叶子,然后跑到 \(u\) 结点,这样一来 \(u\) 这棵子树都不能再通行了。
因此,我们先用 bfs 求出每个点 \(u\) 的 \(d[u]\),然后从根出发,对于访问到的结点,如果满足上面那条式子,就对答案贡献加 1,并且不用继续向下遍历。
这样的复杂度是 \(O(n^2)\) 的。
我们考虑用点分治来优化。
我们可以发现,如果父亲可以选择,那么他们的儿子也一定能被选择,但我们并不需要儿子进行贡献。
我们可以考虑将每个点的权值设为 \(2-r[u]\),其中 \(r[u]\) 指 \(u\) 的度数。(对于根节点,我们权值应该设为 \(1-r[u]\) 才对,但实际上影响的只有那些根节点也为叶节点的情况,因此我们特判即可。)
点分治时,我们处理出点 \(u\) 到重心的距离,记为深度 \(dep[u]\)
对于一个询问 \(u\)(即 \(u\) 为根),如果点 \(v\) 能够被选择,则需要满足 \(d[v]\le dep[u]+dep[v]\)。
我们移项,得到 \(d[v]-dep[v]\le dep[u]\),记 \(sub[u]=d[u]-dep[u]\)
那么我们可以以 \(sub[u]\) 为下标,存入权值 \(2-r[u]\),然后再做一个前缀和。
然后查询 \(dep[u]\) 上的值,贡献到答案即可。
注意容斥掉同一棵子树的情况。
代码
#include<iostream>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#define LL long long
#define INF 0x7fffffff
inline int reads()
{
int sign = 1, re = 0; char c = getchar();
while(c < '0' || c > '9'){if(c == '-') sign = -1; c = getchar();}
while('0' <= c && c <= '9'){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
int n, ans[70005];
std::vector<int> r[70005];
int d[70005];
inline void bfs()
{
static std::queue<int> q;
for(int i = 1; i <= n; i++)
if(r[i].size() == 1) q.push(i);
else d[i] = -1;
while(!q.empty())
{
int now = q.front(); q.pop();
for(int to : r[now])
{
if(d[to] != -1) continue;
d[to] = d[now] + 1;
q.push(to);
}
}
}
int sz[70005], Mx[70005], tot, rt;
std::bitset<70005> vis;
void getrt(int now, int fa)
{
sz[now] = 1, Mx[now] = 0;
for(int to : r[now])
{
if(vis[to] || to == fa) continue;
getrt(to, now);
sz[now] += sz[to], Mx[now] = std::max(Mx[now], sz[to]);
}
Mx[now] = std::max(Mx[now], tot - sz[now]);
if(Mx[now] < Mx[rt]) rt = now;
}
int dep[70005], sub[70005], _val[140005], *val = _val + 70000, tl, tr;
std::vector<int> q, st;
void getdis(int now, int fa)
{
for(int to : r[now])
{
if(vis[to] || to == fa) continue;
dep[to] = dep[now] + 1;
sub[to] = d[to] - dep[to];
q.emplace_back(to);
getdis(to, now);
}
}
inline int query(int x) {return x < tl ? 0 : x > tr ? val[tr] : val[x];}
inline void calc(int now)
{
dep[now] = 0, sub[now] = d[now];
st.emplace_back(now);
for(int to : r[now])
{
if(vis[to]) continue;
dep[to] = 1, sub[to] = d[to] - 1, q.emplace_back(to);
getdis(to, now);
tl = INF, tr = -INF;
for(int i : q) tl = std::min(tl, sub[i]), tr = std::max(tr, sub[i]);
memset(val + tl, 0, sizeof(int) * (tr - tl + 1));
for(int i : q) val[sub[i]] += 2 - r[i].size();
for(int i = tl + 1; i <= tr; i++) val[i] += val[i - 1];
for(int i : q) ans[i] -= query(dep[i]);
st.insert(st.begin(), q.begin(), q.end()), q.clear();
}
tl = INF, tr = -INF;
for(int i : st) tl = std::min(tl, sub[i]), tr = std::max(tr, sub[i]);
memset(val + tl, 0, sizeof(int) * (tr - tl + 1));
for(int i : st) val[sub[i]] += 2 - r[i].size();
for(int i = tl + 1; i <= tr; i++) val[i] += val[i - 1];
for(int i : st) ans[i] += query(dep[i]);
st.clear();
}
void solve(int now)
{
vis[now] = 1, calc(now);
int rp = tot - sz[now];
for(int to : r[now])
{
if(vis[to]) continue;
tot = sz[rt = 0] = (sz[to] > sz[now] ? rp : sz[now]);
getrt(to, 0);
solve(rt);
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
n = reads();
for(int i = 1; i < n; i++)
{
int u = reads(), v = reads();
r[u].emplace_back(v), r[v].emplace_back(u);
} bfs();
tot = Mx[0] = n;
getrt(1, 0);
solve(rt);
for(int i = 1; i <= n; i++) printf("%d\n", ans[i] - (!d[i]));
return 0;
}