P4183 [USACO18JAN]Cow at Large P
要考虑子树不要光顾着想对于一个叶子的情况啊!!
暴力
考虑贝西所在节点固定时怎么做。我们以贝西为根节点,如果某个农夫能从叶节点迅速控制某个子树的根节点,那么这棵子树就只用一个农夫,否则是其儿子的农夫数之和。用式子表示就是:设 \(f_x\) 表示 \(x\) 子树所需要农夫的最少数量,\(g_x\) 表示能到达 \(x\) 的最近叶节点的距离,那么有:
\[f_x = 1,dep_x \ge g_x
\]
\[f_x = \sum_yf_y,dep_x < g_x
\]
树形DP即可。复杂度 \(O(n^2)\)。
正解
考虑如何优化暴力。感觉不是很好优化的样子。我们不去管 \(f_x\) 具体是多少了,我们只去管最终能对答案贡献 1 的子树。我们发现 \(dep_x \ge g_x\) 这个条件有个特点,满足这个条件的一定是一棵棵子树。因此我们可以在子树的顶点上把这个 1 加上。
发现还是不好做。考虑树形差分(好像叫这个),一棵子树(整棵树除外)的点的度数的和为 \(2siz - 1\),即:
\[\sum_{i}d_i = 2siz - 1
\]
移项:
\[\sum_{i}2 - d_i = 1
\]
这样,如果每个点的贡献是 \(2 - d_i\) 的话,整棵子树的贡献就是 1.(神奇)
这样,问题就转化为了:
\[ans_p = \sum_{x}(2 - d_x)[dis(x,p) \ge g_x]
\]
发现这个可以看作点对 \((x, p)\) 之间的关系,因此可以点分治做。加在 \(g_x - dep_x\),查 \(\le dep_p\) 的贡献和。
关键代码:
int d[N];
int g[N];
void dfs_dp(int cur, int faa) {
if (d[cur] == 1) g[cur] = 0;
else g[cur] = n;
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (to == faa) continue;
dfs_dp(to, cur); MIN(g[cur], g[to] + 1);
}
}
void dp_dfs(int cur, int faa) {
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (to == faa) continue;
MIN(g[to], g[cur] + 1); dp_dfs(to, cur);
}
}
int siz[N], Siz, mxSiz, root;
bool vis[N];
void find_root(int cur, int faa)
uint ans[N];
void dfs_calc(int cur, int faa, int nwd) {
ans[cur] += query(nwd);
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (to == faa || vis[to]) continue;
dfs_calc(to, cur, nwd + 1);
}
}
void dfs_add(int cur, int faa, int nwd) {
add(g[cur] - nwd, 2 - d[cur]);
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (to == faa || vis[to]) continue;
dfs_add(to, cur, nwd + 1);
}
}
void dfs_del(int cur, int faa, int nwd) {
del(g[cur] - nwd);
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (to == faa || vis[to]) continue;
dfs_del(to, cur, nwd + 1);
}
}
int stk[N], stop;
void dfs(int cur) {
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (vis[to]) continue;
dfs_calc(to, cur, 1);
dfs_add(to, cur, 1); stk[++stop] = to;
}
ans[cur] += query(0);
dfs_del(cur, 0, 0);
add(g[cur], 2 - d[cur]);
for (register int i = stop; i; --i) {
int to = stk[i]; dfs_calc(to, cur, 1); dfs_add(to, cur, 1);
}
stop = 0;
dfs_del(cur, 0, 0);
vis[cur] = true;
for (register int i= head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (vis[to]) continue;
Siz = siz[to], mxSiz = n;
find_root(to, cur);
dfs(root);
}
}
int main() {
read(n); nn = n + n;
for (register int i = 1; i < n; ++i) {
int u, v; read(u), read(v);
addedge(u, v), addedge(v, u);
++d[u]; ++d[v];
}
dfs_dp(1, 0); dp_dfs(1, 0);
Siz = mxSiz = n;
find_root(1, 0);
dfs(root);
for (register int i = 1; i <= n; ++i) {
if (d[i] == 1) ans[i] = 1;
Print(ans[i]);
}
return 0;
}