題解[NOIP2016 提高組] 天天愛跑步
阿新 • • 發佈:2021-08-04
算是NOIP中比較麻煩的題了,看題解感覺處理的很巧妙
題意就不再贅述了,剛開始的想法是遍歷列舉每一條路徑,但是無論如何這樣做的複雜度最壞都有O(nm)
所以嘗試換一種方法,從觀察員下手,對於每一個觀察員,我們只需要找到每一條路徑帶給他的貢獻
那這個貢獻怎麼求呢?
對於每一條路徑(u,v),我們都可以將它拆為 u->lca 與 lca ->v
假設觀察員的位置p在 u-> lca 上,當depth[u]+w[p]=depth[p]時才會有貢獻
而當p在 lca->v 上時,只有dist(u,v)-(depth[v]-depth[p])=w[p],即dist(u,v)-depth[v]=w[p]-depth[p]時才有貢獻
然後是統計貢獻,如果我們在統計貢獻時也一個一個去計算,那麼複雜度就又上去了,所以這個時候可以用一個桶來處理,
這也是這道題比較巧妙的一點,
先看u->lca,對於點x,我們將它貢獻的答案存在cnt[depth[x]]裡,對於這條路徑上的點p,更新答案就只需要加上cnt[depth[p]+w[p]]
對於lca->v,我們就將答案貢獻存在cnt[dist(u,v)-depth[v]]裡,同理對於p,更新答案加上cnt[w[p]-depth[p]]
然後是這道題中的一些細節部分,結合程式碼理解
#include<iostream> #include<cstdio> using namespace std; const int N = 3e5 + 5; int cnt[N], ans[N], dist[N],t1[2 * N]; int w[N], s[N], t[N], fa[N][21], dep[N], t2[2 * N]; int head[N], ver[2 * N], net[2 * N], idx; int head1[N], ver1[2 * N], net1[2 * N], idx1; int head2[N], ver2[2 * N], net2[2 * N], idx2; void add(int a, int b) { net[++idx] = head[a], ver[idx] = b, head[a] = idx; } void add1(int a, int b) { net1[++idx1] = head1[a], ver1[idx1] = b, head1[a] = idx1; } void add2(int a, int b) { net2[++idx2] = head2[a], ver2[idx2] = b, head2[a] = idx2; } void dfs1(int u, int f) { fa[u][0] = f, dep[u] = dep[f] + 1; for (int i = 1; i <= 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int i = head[u]; i; i = net[i]) { int v = ver[i]; if (v == f) continue; dfs1(v, u); } } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); for (int i = 20; i >= 0; i--) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i]; if (u == v) return u; for (int i = 20; i >= 0; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return fa[u][0]; }//求lca的部分,不作講解 void dfs2(int u) { int tp1 = t1[dep[u] + w[u]], tp2 = t2[w[u] - dep[u] + N];//先存下來,接下來有用 for (int i = head[u]; i; i = net[i]) { int v = ver[i]; if (v == fa[u][0]) continue; dfs2(v); } t1[dep[u]] += cnt[u]; for (int i = head1[u]; i; i = net1[i]) { int v = ver1[i]; t2[dist[v] - dep[t[v]] + N]++; }//更新貢獻 ans[u] += t1[dep[u] + w[u]] - tp1 + t2[w[u] - dep[u] + N] - tp2; //tp1,tp2是之前計算的貢獻,對於一條路徑,只有帶給以該路徑的lca為根的子樹內的點貢獻,所以減去之前已經沒有用的貢獻 for (int i = head2[u]; i; i = net2[i]) { int v = ver2[i]; t1[dep[s[v]]]--, t2[dist[v] - dep[t[v]] + N]--;//這個點沒用了,將貢獻減去 } } int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); add(u, v), add(v, u); } for (int i = 1; i <= n; i++) scanf("%d", &w[i]); dfs1(1, 0); for (int i = 1; i <= m; i++) { scanf("%d%d", &s[i], &t[i]); int u = lca(s[i], t[i]); dist[i] = dep[s[i]] + dep[t[i]] - 2 * dep[u];//計算路徑長度 cnt[s[i]]++;//統計每個結點作為起點的路徑條數 add1(t[i], i);//建立一個終點與路徑的圖 add2(u, i);//建立一個lca與路徑的圖 if (dep[u] + w[u] == dep[s[i]])//這裡說一下,這種情況是u,v是一條鏈的情況,這種情況u->lca與lca->v會重複計算答案,所以減一 ans[u]--; } dfs2(1); for (int i = 1; i <= n; i++) printf("%d ", ans[i]); return 0; }