CF1452G Game On Tree
CF1452G Game On Tree
題目來源:Codeforces, Educational Codeforces Round 98 (Rated for Div. 2), CF1452G Game On Tree
題目大意
Alice 和 Bob 在玩一個遊戲。他們有一棵由 \(n\) 個結點組成的樹。一開始,Bob 有 \(k\) 個卡片,其中第 \(i\) 個卡片位於結點 \(a_i\)(注意:所有的結點都是唯一的)。在遊戲開始之前,Alice 將在這棵樹的一個結點上放置一個卡片。
這個遊戲由一些回合組成。每個回合都將有以下事件發生(完全按照以下順序):
- Alice 可以把她的卡片移到相鄰的結點,或者不移動;
- 對於 Bob 的每一張卡片,他可以把這張卡片移到相鄰的結點,或者不移動。注意:每個卡片的選擇都是獨立的。
當 Alice 的卡片與 Bob 的任意一張(或多張)卡片在同一結點時,遊戲結束。(Bob自己的多張卡片可以置於同一結點上,即使它們的初始位置一定是不同的)。
Alice希望遊戲回合越多越好,Bob則相反。如果某回合中間遊戲結束(即 Alice 把卡片移到了有 Bob 卡片的結點上),這回合依然算入總回合數。
對於每個結點,計算 Alice 一開始將卡片放在該結點時遊戲將持續的回合數。
資料範圍:\(2\leq n\leq 2\times 10^5\),\(1\leq k < n\)
本題題解
設樹上兩點 \(u,v\) 間的距離為 \(\text{dis}(u,v)\)。
考慮對某個 Alice 的起點 \(a\) 求答案。先把 \(a\) 提作樹根。
設 Bob 的所有起點中,離 \(u\) 最近的點與 \(u\) 的距離為 \(\text{disBob}(u)\)。
考慮 Bob 的移動策略,顯然他一定讓所有點同時向當前 Alice 所在的位置走。
與此同時 Alice 要避免被 Bob 追到,她會向她能到達的點裡,\(\text{disBob}(u)\) 最大的點走。“她能到達的點”指的是在 Alice 從起點 \(a\) 走到這個點的過程中,不會被 Bob 逮到。具體來說,一個點 \(u\)
對所有點 \(u\in[1,n]\) 預處理出 \(\text{disBob}(u)\),可以通過樹形 DP + 換根預處理,時間複雜度 \(O(n)\)。
然後對每個 \(a\) 求答案,只需要從 \(a\) 出發 dfs 一遍,求出所有“能到達的點” \(u\) 的 \(\text{disBob}(u)\) 的最大值。總時間複雜度 \(O(n^2)\)。無法通過本題。
考慮優化上述過程。將點按 \(\text{disBob}(u)\) 從小到大排成一個序列 \(p_1,p_2,\dots,p_n\)(\(p\) 是一個 \(1\dots n\) 的排列且 \(\text{disBob}(p_1)\leq \text{disBob}(p_2)\leq \dots\leq \text{disBob}(p_n)\)。對每個點 \(a\),在這個序列上二分答案。問題轉化為求 \(p_{\text{mid}+1},\dots ,p_{r}\) 中,是否存在點 \(p_i\) 是 \(a\) “能到達”的。
單獨二分似乎難以操作。我們對所有 \(a\) 整體二分。具體來說,我們定義一個遞迴的函式 \(\text{solve}(Q,l,r)\) 表示 \(Q\) 集合裡的這些 \(a\),答案都在 \(l,r\) 之間。若 \(l=r\),則直接記下答案,然後返回。否則設 \(\text{mid} = \lfloor\frac{l+r}{2}\rfloor\)。我們對 \(Q\) 集合裡的所有 \(a\),判斷:集合 \(P = \{p_{\text{mid}+1},\dots,p_{r}\}\) 中,是否存在點 \(p_i\) 是 \(a\) “能到達”的。
要一次性判斷所有 \(a\),可以對 \(Q\cup P\) 這些點,建出虛樹。然後做樹形 DP。設 \(f(u)\) 表示 \(u\) 子樹裡所有 \(P\) 集合裡的點 \(v\) 中,\(\text{dis}(v,u) - \text{disBob}(v)\) 的最小值。
對於根節點 \(\text{root}\),如果 \(f(\text{root}) < 0\),則 \(\text{root}\) 的答案在 \([\text{mid}+1,r]\) 中;如果 \(f(\text{root}) > 0\) 則它的答案在 \([l,\text{mid}]\) 中。換根後,即可對所有點分別做出這樣的判斷。然後將 \(Q\) 劃分為兩個集合 \(Q_L,Q_R\),分別遞迴呼叫:\(\text{solve}(Q_L,l,\text{mid})\),\(\text{solve}(Q_R,\text{mid} + 1, r)\) 即可。
需要注意的是,\(f(\text{root}) = 0\) 的情況是比較特殊的。它能放在 \(Q_R\),當且僅當存在一個點 \(u\in P\),滿足 \(\text{dis}(u,\text{root}) = \text{disBob}(u)\),且 \(u\) 向 \(\text{root}\) 方向的兒子子樹裡(也就是以 \(\text{root}\) 為根時,\(u\) 子樹以外的所有點),不存在一個 Bob 的起點 \(b\) 使得 \(\text{dis}(b,u) = \text{disBob}(u)\)。這也可以通過做一個類似的樹形 DP:只要 \(u\) 的子樹外,存在到 \(u\) 距離等於 \(\text{disBob}(u)\) 的 Bob 的起點,我們就不讓 \(u\) 對父親的 DP 值產生貢獻(假裝 \(u\) 不在 \(P\) 集合裡)。然後再換根一次即可。
發現在遞迴的過程中,每個節點只會遞迴 \(O(\log n)\) 層(因為是個二分),每層裡要建虛樹,所以總時間複雜度 \(O(n\log^2n)\)。
參考程式碼
// problem: CF1452G
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
/* --------------- fast io --------------- */ // begin
namespace Fread {
const int SIZE = 1 << 21;
char buf[SIZE], *S, *T;
inline char getchar() {
if (S == T) {
T = (S = buf) + fread(buf, 1, SIZE, stdin);
if (S == T) return EOF;
}
return *S++;
}
} // namespace Fread
namespace Fwrite {
const int SIZE = 1 << 21;
char buf[SIZE], *S = buf, *T = buf + SIZE;
inline void flush() {
fwrite(buf, 1, S - buf, stdout);
S = buf;
}
inline void putchar(char c) {
*S++ = c;
if (S == T) flush();
}
struct NTR {
~ NTR() { flush(); }
}ztr;
} // namespace Fwrite
#ifdef ONLINE_JUDGE
#define getchar Fread :: getchar
#define putchar Fwrite :: putchar
#endif
namespace Fastio {
struct Reader {
template<typename T>
Reader& operator >> (T& x) {
char c = getchar();
T f = 1;
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
x = 0;
while (c >= '0' && c <= '9') {
x = x * 10 + (c - '0');
c = getchar();
}
x *= f;
return *this;
}
Reader& operator >> (char& c) {
c = getchar();
while (c == '\n' || c == ' ') c = getchar();
return *this;
}
Reader& operator >> (char* str) {
int len = 0;
char c = getchar();
while (c == '\n' || c == ' ') c = getchar();
while (c != '\n' && c != ' ') {
str[len++] = c;
c = getchar();
}
str[len] = '\0';
return *this;
}
Reader(){}
}cin;
const char endl = '\n';
struct Writer {
template<typename T>
Writer& operator << (T x) {
if (x == 0) { putchar('0'); return *this; }
if (x < 0) { putchar('-'); x = -x; }
static int sta[45];
int top = 0;
while (x) { sta[++top] = x % 10; x /= 10; }
while (top) { putchar(sta[top] + '0'); --top; }
return *this;
}
Writer& operator << (char c) {
putchar(c);
return *this;
}
Writer& operator << (char* str) {
int cur = 0;
while (str[cur]) putchar(str[cur++]);
return *this;
}
Writer& operator << (const char* str) {
int cur = 0;
while (str[cur]) putchar(str[cur++]);
return *this;
}
Writer(){}
}cout;
} // namespace Fastio
#define cin Fastio :: cin
#define cout Fastio :: cout
#define endl Fastio :: endl
/* --------------- fast io --------------- */ // end
const int MAXN = 2e5;
const int INF = 1e9;
int n, K, a[MAXN + 5];
bool mark[MAXN + 5];
struct EDGE { int nxt, to; } edge[MAXN * 2 + 5];
int head[MAXN + 5], tot;
inline void add_edge(int u, int v) { edge[++tot].nxt = head[u]; edge[tot].to = v; head[u] = tot; }
namespace TreeChainPartition {
int fa[MAXN + 5], sz[MAXN + 5], son[MAXN + 5], dep[MAXN + 5];
void dfs1(int u) {
sz[u] = 1;
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (v == fa[u])
continue;
fa[v] = u;
dep[v] = dep[u] + 1;
dfs1(v);
sz[u] += sz[v];
if (!son[u] || sz[v] > sz[son[u]])
son[u] = v;
}
}
int top[MAXN + 5], dfn[MAXN + 5], ofn[MAXN + 5], rev[MAXN + 5], cnt_dfn;
void dfs2(int u, int t) {
top[u] = t;
dfn[u] = ++cnt_dfn;
rev[cnt_dfn] = u;
if (son[u])
dfs2(son[u], t);
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (v == fa[u] || v == son[u])
continue;
dfs2(v, v);
}
ofn[u] = cnt_dfn;
}
int get_lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])
swap(u, v);
u = fa[top[u]];
}
return (dep[u] < dep[v]) ? u : v;
}
int jump(int u, int _anc) {
while (top[u] != top[_anc]) {
if (fa[top[u]] == _anc) {
return top[u];
}
u = fa[top[u]];
}
return rev[dfn[_anc] + 1];
}
void init() {
dfs1(1);
dfs2(1, 1);
}
} // namespace TreeChainPartition
using TreeChainPartition :: dep;
using TreeChainPartition :: dfn;
using TreeChainPartition :: get_lca;
using TreeChainPartition :: jump;
int bob_dis[MAXN + 5];
int ans[MAXN + 5];
vector<int> vec[MAXN + 5];
void dfs1(int u, int fa) {
vec[u].pb(INF);
if (mark[u]) {
vec[u].pb(0);
}
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (v == fa)
continue;
dfs1(v, u);
if (bob_dis[v] != INF)
vec[u].pb(bob_dis[v] + 1);
}
sort(vec[u].begin(), vec[u].end());
bob_dis[u] = vec[u][0];
}
int bob_dis_from_fa[MAXN + 5];
bool flag[MAXN + 5];
void dfs2(int u, int fa) {
if (fa) {
int ff;
if (bob_dis[u] + 1 == vec[fa][0]) {
ff = vec[fa][1];
} else {
ff = vec[fa][0];
flag[u] = 1;
}
bob_dis_from_fa[u] = ff;
if (ff != INF) {
vec[u].pb(ff + 1);
}
sort(vec[u].begin(), vec[u].end());
bob_dis[u] = vec[u][0];
}
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (v == fa)
continue;
dfs2(v, u);
}
}
int nodes_sorted_by_bob_dis[MAXN + 5];
bool cmp_by_bob_dis(int u, int v) { return bob_dis[u] < bob_dis[v]; }
struct VIREDGE { int nxt, to, w; } vir_edge[MAXN * 2 + 5]; // 虛樹
int vir_head[MAXN + 5], vir_tot;
int tim[MAXN + 5], TIM;
inline void vir_add_edge(int u, int v, int w) {
if (tim[u] < TIM) {
tim[u] = TIM;
vir_head[u] = 0; // 懶惰清空
}
if (tim[v] < TIM) {
tim[v] = TIM;
vir_head[v] = 0;
}
vir_edge[++vir_tot].nxt = vir_head[u];
vir_edge[vir_tot].to = v;
vir_edge[vir_tot].w = w;
vir_head[u] = vir_tot;
}
int sta[MAXN + 5], top;
int nodes_sorted_by_dfn[MAXN * 2 + 5], cnt;
bool cmp_by_dfn(int u, int v) { return dfn[u] < dfn[v]; }
void add_node(int u) {
if (!top) {
sta[++top] = u;
return;
}
int lca = get_lca(sta[top], u);
if (lca == sta[top]) {
sta[++top] = u;
return;
}
while (top >= 2 && dep[sta[top - 1]] >= dep[lca]) {
vir_add_edge(sta[top - 1], sta[top], dep[sta[top]] - dep[sta[top - 1]]);
--top;
}
if (sta[top] == lca) {
sta[++top] = u;
return;
}
vir_add_edge(lca, sta[top], dep[sta[top]] - dep[lca]);
sta[top] = lca;
sta[++top] = u;
}
int fw[MAXN + 5];
int f[MAXN + 5], g[MAXN + 5];
struct ThreeMin {
int a, b, c;
void insert(int x) {
if (x < a) {
c = b;
b = a;
a = x;
} else if (x < b) {
c = b;
b = x;
} else if (x < c) {
c = x;
}
}
void erase(int x) {
if (x == b) {
b = c;
c = INF;
} else if (x == a) {
a = b;
b = c;
c = INF;
}
}
void init() {
a = b = c = INF;
}
ThreeMin() { init(); }
};
ThreeMin vf[MAXN + 5], vg[MAXN + 5];
void dfs3(int u) {
vf[u].init();
vg[u].init();
if (mark[u]) {
vf[u].insert(-bob_dis[u]);
if (bob_dis_from_fa[u] + 1 != bob_dis[u]) {
vg[u].insert(-bob_dis[u]);
}
}
for (int i = vir_head[u]; i; i = vir_edge[i].nxt) {
int v = vir_edge[i].to;
fw[v] = vir_edge[i].w;
dfs3(v);
if (f[v] != INF)
vf[u].insert(f[v] + vir_edge[i].w);
if (g[v] != INF)
vg[u].insert(g[v] + vir_edge[i].w);
}
f[u] = vf[u].a;
g[u] = vg[u].a;
}
void dfs4(int u, int fa) {
if (fa) {
int ff;
if (f[u] + fw[u] == vf[fa].a) {
ff = vf[fa].b;
} else {
ff = vf[fa].a;
}
if (ff != INF) {
vf[u].insert(ff + fw[u]);
}
f[u] = vf[u].a;
int gg;
if (g[u] + fw[u] == vg[fa].a) {
gg = vg[fa].b;
} else {
gg = vg[fa].a;
}
if (mark[fa] && flag[jump(u, fa)]) {
ckmin(gg, -bob_dis[fa]);
}
if (gg != INF) {
vg[u].insert(gg + fw[u]);
}
g[u] = vg[u].a;
}
if (mark[u] && bob_dis_from_fa[u] + 1 != bob_dis[u]) {
vg[u].erase(-bob_dis[u]);
}
for (int i = vir_head[u]; i; i = vir_edge[i].nxt) {
int v = vir_edge[i].to;
dfs4(v, u);
}
}
void solve(const vector<int>& q, int l, int r) {
// q 中這些詢問的答案在 [l, r] 之間
if (!SZ(q))
return;
if (l == r || bob_dis[nodes_sorted_by_bob_dis[l]] == bob_dis[nodes_sorted_by_bob_dis[r]]) {
for (int i = 0; i < SZ(q); ++i) {
ans[q[i]] = l;
}
return;
}
int mid = (l + r) >> 1;
cnt = 0;
for (int i = 0; i < SZ(q); ++i) {
nodes_sorted_by_dfn[++cnt] = q[i];
}
for (int i = mid + 1; i <= r; ++i) {
nodes_sorted_by_dfn[++cnt] = nodes_sorted_by_bob_dis[i];
mark[nodes_sorted_by_bob_dis[i]] = 1;
}
sort(nodes_sorted_by_dfn + 1, nodes_sorted_by_dfn + cnt + 1, cmp_by_dfn);
cnt = unique(nodes_sorted_by_dfn + 1, nodes_sorted_by_dfn + cnt + 1) - (nodes_sorted_by_dfn + 1);
top = 0;
if (nodes_sorted_by_dfn[1] != 1)
add_node(1);
for (int i = 1; i <= cnt; ++i)
add_node(nodes_sorted_by_dfn[i]);
for (int i = 2; i <= top; ++i)
vir_add_edge(sta[i - 1], sta[i], dep[sta[i]] - dep[sta[i - 1]]);
dfs3(1);
dfs4(1, 0);
vir_tot = 0;
TIM++; // 懶惰清空
for (int i = mid + 1; i <= r; ++i) {
mark[nodes_sorted_by_bob_dis[i]] = 0;
}
vector<int> ql, qr;
for (int i = 0; i < SZ(q); ++i) {
if (f[q[i]] < 0 || (f[q[i]] == 0 && g[q[i]] == 0)) {
qr.pb(q[i]);
} else {
ql.pb(q[i]);
}
}
solve(ql, l, mid);
solve(qr, mid + 1, r);
}
int main() {
cin >> n;
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
add_edge(u, v);
add_edge(v, u);
}
TreeChainPartition :: init();
cin >> K;
for (int i = 1; i <= K; ++i) {
cin >> a[i];
mark[a[i]] = 1;
}
dfs1(1, 0);
dfs2(1, 0);
bob_dis_from_fa[1] = INF;
// for (int i = 1; i <= n; ++i) cerr << bob_dis[i] << " \n"[i == n];
for (int i = 1; i <= K; ++i) mark[a[i]] = 0;
vector<int> q;
for (int i = 1; i <= n; ++i) q.pb(i);
for (int i = 1; i <= n; ++i) nodes_sorted_by_bob_dis[i] = i;
sort(nodes_sorted_by_bob_dis + 1, nodes_sorted_by_bob_dis + n + 1, cmp_by_bob_dis);
solve(q, 1, n);
for (int i = 1; i <= n; ++i) {
cout << bob_dis[nodes_sorted_by_bob_dis[ans[i]]] << " \n"[i == n];
}
return 0;
}