bzoj3572 世界樹(虛樹,倍增)
阿新 • • 發佈:2021-10-19
解題思路
題目的問法很明顯是虛樹題的問法。根據詢問點建虛樹,因為虛樹不止有詢問的點,還有他們的lca,所以先對建出來的虛樹預處理出來虛樹上每個點離他最近的議事處的距離和點的編號(自己就是議事處肯定就是自己,主要是對增加的lca進行處理)。因為最近的點可能來自下面的點也可能是上面的點,所以跑兩次dfs從兩個方向更新一下即可。
對於虛樹的一條邊來說,兩個點都有其對應的議事處,它們之間的邊代表原樹上的一些點(或者說子樹)。對於每條邊我們都計算一下兩個點對應的議事處的分界點(注意不是這個點本身,以為它可能不是議事處而是新增的lca)即可。方法是用倍增從深度最大的那個點往上跳,計算跳到的那個點當分界點時到兩個議事處的距離,注意還要考慮距離相等編號選最小的議事處。
還有一個細節需要注意,對於點u和點v(u是v的祖先),我們計算分界點d分開的點的數量不能直接加上sz[u]-sz[d],因為u有好幾個子樹,這樣加肯定會重複,可以算出u的兒子son,滿足son是v的祖先,這樣每次減去sz[son],加上sz[son]-sz[d],最後再加上sz[u]就行了。
程式碼
const int maxn = 6e5+10; struct E { int to, nxt; } e[maxn]; int h[maxn], tot; void add(int u, int v) { e[++tot] = {v, h[u]}; h[u] = tot; } int ldfn[maxn], rdfn[maxn], tim; int dep[maxn], f[maxn][20], sz[maxn]; void dfs(int u, int p) { //cout << "!" << u << endl; ldfn[u] = ++tim; sz[u] = 1; for (int i = h[u]; i; i=e[i].nxt) { int v = e[i].to; if (v==p) continue; dep[v] = dep[u]+1; f[v][0] = u; for (int j = 1; j<20; ++j) f[v][j] = f[f[v][j-1]][j-1]; dfs(v, u); sz[u] += sz[v]; } rdfn[u] = tim; } int lca(int u, int v) { if (dep[u]<dep[v]) swap(u, v); for (int i = 19; i>=0; --i) if (dep[f[u][i]]>=dep[v]) u = f[u][i]; if (u==v) return u; for (int i = 19; i>=0; --i) if (f[u][i]!=f[v][i]) u = f[u][i], v = f[v][i]; return f[u][0]; } int n, m, k, d[maxn], sk[maxn], vis[maxn]; void build() { tot = 0; //!!!! sort(d+1, d+k+1, [](int a, int b) {return ldfn[a]<ldfn[b];}); //for (int i = 1; i<=k; ++i) cout << d[i] << (i==k ? '\n':' '); int keynum = k; for (int i = 1; i<keynum; ++i) d[++k] = lca(d[i], d[i+1]); sort(d+1, d+k+1, [](int a, int b) {return ldfn[a]<ldfn[b];}); k = unique(d+1, d+k+1)-d-1; int tp = 1; sk[tp] = d[1]; for (int i = 2; i<=k; ++i) { while(tp && rdfn[sk[tp]]<ldfn[d[i]]) --tp; if (tp) add(sk[tp], d[i]); sk[++tp] = d[i]; } } int dp[maxn], g[maxn]; void dfs1(int u, int p) { dp[u] = INF; for (int i = h[u]; i; i=e[i].nxt) { int v = e[i].to; if (v==p) continue; dfs1(v, u); int dis = dep[v]-dep[u]; //cout << dis << endl; //注意相同距離取編號小的 if (dp[v]+dis<dp[u] || (dp[v]+dis==dp[u]&&g[v]<g[u])) dp[u] = dp[v]+dis, g[u] = g[v]; //cout << "!" << u << ' ' << v << ' ' << g[u] << ' ' << dp[u] << endl; } if (vis[u]) dp[u] = 0, g[u] = u; } void dfs2(int u, int p) { for (int i = h[u]; i; i=e[i].nxt) { int v = e[i].to; if (v==p) continue; int dis = dep[v]-dep[u]; //cout << dis << endl; //注意相同距離取編號小的 if (dp[u]+dis<dp[v] || (dp[u]+dis==dp[v]&&g[u]<g[v])) dp[v] = dp[u]+dis, g[v] = g[u]; dfs2(v, u); } } int ans[maxn], tmp[maxn]; void calc(int u, int v) { int son = v; for (int i = 19; i>=0; --i) if (dep[f[son][i]]>dep[u]) son = f[son][i]; ans[g[u]] -= sz[son]; //先減去兒子的貢獻 //cout << ans[g[u]] << endl; int d = v; for (int i = 19; i>=0; --i) { int t = f[d][i]; if (dep[t]<dep[u]) continue; //倍增往上找分界點 int l = dp[v]+dep[v]-dep[t], r = dp[u]+dep[t]-dep[u]; if (l<r || (l==r && g[v]<g[u])) d = t; //相同距離取最小 } //cout << u << ' ' << g[u] << "|||" << v << ' ' << g[v] << "!!" << d << endl; ans[g[u]] += sz[son]-sz[d]; ans[g[v]] += sz[d]-sz[v]; //cout << ans[g[u]] << ' ' << ans[g[v]] << endl; } void dfs3(int u, int p) { for (int i = h[u]; i; i=e[i].nxt) { int v = e[i].to; if (v==p) continue; calc(u, v); dfs3(v, u); } ans[g[u]] += sz[u]; } void init() { tot = 0; for (int i = 1; i<=k; ++i) h[d[i]] = ans[d[i]] = g[d[i]] = vis[d[i]] = 0, dp[d[i]]= INF; } int main() { cin >> n; for (int i = 1, a, b; i<n; ++i) { scanf("%d%d", &a, &b); add(a, b); add(b, a); } dep[1] = 1; dfs(1, 0); //for (int i = 1; i<=n; ++i) cout << ldfn[i] << ' ' << rdfn[i] << endl; clr(h, 0); tot = 0; //清空原樹 cin >> m; while(m--) { scanf("%d", &k); int tk = k; for (int i = 1; i<=k; ++i) scanf("%d", &d[i]), tmp[i] = d[i]; for (int i = 1; i<=k; ++i) vis[d[i]] = 1; if (!vis[1]) d[++k] = 1; build(); //for (int i = 1; i<=k; ++i) printf(i==k ? "%d\n":"%d ", d[i]); dfs1(1, 0); //for (int i = 1; i<=k; ++i) cout << d[i] << ' ' << g[d[i]] << endl; dfs2(1, 0); dfs3(1, 0); //for (int i = 1; i<=k; ++i) cout << d[i] << ' ' << g[d[i]] << endl; for (int i = 1; i<=tk; ++i) printf(i==tk ? "%d\n":"%d ", ans[tmp[i]]); init(); } return 0; }