noip2016 天天愛跑步
分析:這道題真心煩啊,是我做過noip真題中難度最高的一道了,到今天為止才把noip2016的坑給填滿.暴力的話前60分應該是可以拿滿的,後40分還是很有難度的.
定義:每個人的起點、終點:s,t;深度:deep[i];觀察員出現時間:w[i];
首先,樹上兩個點的最短路徑肯定要經過LCA,那麽對於路徑x ---> y我們可以分成兩部分:1.x ---> lca. 2.lca ---> y.先分析第一段路上的觀察員i,顯然s到i的距離等於w[i]才行,這段路是從x向上跳的,所以可以得到式子:
deep[s] - deep[i] = w[i].
移項,得到:
deep[s] = deep[i] + w[i].
可以發現右邊是固定的,那麽我們只需要找出以i為根的子樹中深度為deep[i] + w[i]的點中,有多少起點.對於子樹的修改查詢,我們通常轉換為區間來做,方法是dfs序+線段樹。只是這個線段樹的姿勢有點特別:動態加點,每一個深度建一棵線段樹。
現在我們把第一段路簡化為區間[a,b],當有一個人的起點滿足條件時,我們要使[a,b] + 1,具體怎麽實現呢?可以參考noip2012借教室,利用差分思想,在s[a]處+1,在s[b + 1]處-1,這個操作是在線段樹上完成的,這樣就避免了區間修改,而變成了單點修改,主要是為了避免使用lazy標記.
操作完後,每次查詢深度為deep[i] + w[i]的深度的點中有多少起點就好了.
下面分析第二段路,其實操作類似,不過就是式子變了:
deep[s] + deep[i] - 2*deep[lca(s,i)] = w[i].
這個式子也是比較顯然的,在圖上推一下就可以得到,移項也可以得到:
deep[s] - 2*deep[lca(s,i)] = w[i] - deep[i]
然後的處理方法就和上面是一樣的,不過要註意,式子左邊可能會等於負數,那麽我們將下標都向右移2*n位就可以了.
參考:傳送門,這位神犇用的方法是樹鏈剖分求lca,不過我的是用的倍增,其實都差不多啦.
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<cmath> #include<map> using namespace std; const int inf = 0x7ffffff; int n, m, tot = 1, head[300010], to[600010], nextt[600010], w[300010], dfs_clock, in[300010], out[300010], deep[300010], fa[300010][21], id[300010]; int root[300010 * 3], lc[300010 * 25], rc[300010 * 25], ans[300010], cnt, sum[300010 * 25]; struct node { int s, t, lca; }e[300010]; void add(int x, int y) { to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void dfs(int u, int d, int from) { in[u] = ++dfs_clock; id[u] = dfs_clock; deep[u] = d; for (int i = head[u]; i; i = nextt[i]) { int v = to[i]; if (v != from) { dfs(v, d + 1, u); fa[v][0] = u; } } out[u] = dfs_clock; } int getlca(int x, int y) { if (x == y) return x; if (deep[x] < deep[y]) swap(x, y); for (int j = 19; j >= 0; j--) if (deep[fa[x][j]] >= deep[y]) x = fa[x][j]; if (x == y) return x; for (int j = 19; j >= 0; j--) if (fa[x][j] != fa[y][j]) { x = fa[x][j]; y = fa[y][j]; } return fa[x][0]; } void init() { cnt = 0; memset(lc, 0, sizeof(lc)); memset(rc, 0, sizeof(rc)); memset(sum, 0, sizeof(sum)); memset(root, 0, sizeof(root)); } void update(int &o, int l, int r, int p, int v) { if (!p) return; if (!o) o = ++cnt; sum[o] += v; if (l == r) return; int mid = (l + r) >> 1; if (p <= mid) update(lc[o], l, mid, p, v); else update(rc[o], mid + 1, r, p, v); } int query(int o, int l, int r, int x, int y) { if (!o) return 0; if (x <= l && r <= y) return sum[o]; int mid = (l + r) >> 1, res = 0; if (x <= mid) res += query(lc[o], l, mid, x, y); if (y > mid) res += query(rc[o], mid + 1, r, x, y); return res; } int main() { 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]); for (int i = 1; i <= m; i++) scanf("%d%d", &e[i].s, &e[i].t); dfs(1, 1, 0); //預處理出dfs序和深度和fa數組 /* for (int i = 1; i <= n; i++) printf("%d %d %d\n", i, in[i], out[i]); */ for (int j = 1; j <= 19; j++) for (int i = 1; i <= n; i++) fa[i][j] = fa[fa[i][j - 1]][j - 1]; for (int i = 1; i <= m; i++) e[i].lca = getlca(e[i].s, e[i].t); for (int i = 1; i <= m; i++) { int tt = deep[e[i].s]; update(root[tt], 1, n, id[e[i].s], 1); update(root[tt], 1, n, id[fa[e[i].lca][0]], -1); } for (int i = 1; i <= n; i++) ans[i] = query(root[deep[i] + w[i]], 1, n, in[i], out[i]); init(); //第一次線段樹後一定要清空 for (int i = 1; i <= m; i++) { int tt = deep[e[i].s] - deep[e[i].lca] * 2 + n * 2; update(root[tt], 1, n, id[e[i].t], 1); update(root[tt], 1, n, id[e[i].lca], -1); //關於這裏為什麽不用fa[lca][0],目的是為了避免重復計算lca的貢獻. } for (int i = 1; i <= n; i++) ans[i] += query(root[w[i] - deep[i] + n * 2], 1, n, in[i], out[i]); for (int i = 1; i <= n; i++) printf("%d ", ans[i]); return 0; }
noip2016 天天愛跑步