【做題】51Nod1766樹上的最遠點對——直徑&線段樹
阿新 • • 發佈:2018-11-01
原文連結 https://www.cnblogs.com/cly-none/p/9890837.html
題意:給出一棵大小為\(n\)的樹,邊有邊權。\(m\)次詢問,每次給出兩個標號區間\([a,b]\)和\([c,d]\),求\(\max {dis(i,j) \ | \ a \leq i \leq b, \, c \leq j \leq d }\)。
\(n,m \leq 10^5\)
本題主要是對直徑性質的運用。
先考慮這樣一個結論。
對於兩個點集\(A\)和\(B\),如果\(A\)的最遠點對是\((a,b)\),\(B\)的最遠點對是\((c,d)\),那麼,點集\(A \bigcup B\)
的最遠點對的兩個點一定是\(a,b,c,d\)中的兩個。
至於證明,可以考慮直徑的性質:到任何一個結點的最遠點一定是兩個直徑端點中的一個。
注:一個點集的最遠點對可以通過構建虛樹轉化為直徑。
那麼,考慮\(A\)中的一個結點,在\(B\)集合,到它最遠的結點一定可以是\(c,d\)中的一個。否則,我們可以反證\((c,d)\)不是\(B\)中的最遠點對。那麼,在\(A \bigcup B\)中,任何一個結點,到它的最遠點一定是\(a,b,c,d\)中的一個。因此,最遠點對就一定是\(a,b,c,d\)中的兩個。
於是,我們可以用線段樹維護區間的最遠點對。再根據我們的結論,\([c,d]\)
時間複雜度\(O(n \log n)\)。
#include <bits/stdc++.h> using namespace std; #define gc() getchar() template <typename tp> inline void read(tp& x) { x = 0; char tmp; bool key = 0; for (tmp = gc() ; !isdigit(tmp) ; tmp = gc()) key = (tmp == '-'); for ( ; isdigit(tmp) ; tmp = gc()) x = (x << 3) + (x << 1) + tmp - '0'; if (key) x = -x; } const int N = 100010, MP = 19; struct edge { int la,b,v; } con[N << 1]; int tot,fir[N],n,m; void add(int from,int to,int val) { con[++tot] = (edge) {fir[from],to,val}; fir[from] = tot; } int dep[N],dfn[N << 1],dcnt,mn[N << 1][MP],ln[N << 1],rec[N],dis[N]; int lca(int x,int y) { x = rec[x], y = rec[y]; if (x > y) swap(x,y); int len = ln[y - x + 1]; return dep[dfn[mn[y][len]]] < dep[dfn[mn[x + (1 << len) - 1][len]]] ? dfn[mn[y][len]] : dfn[mn[x + (1 << len) - 1][len]]; } void dfs(int pos,int fa) { dep[pos] = dep[fa] + 1; dfn[rec[pos] = ++dcnt] = pos; for (int i = fir[pos] ; i ; i = con[i].la) { if (con[i].b == fa) continue; dis[con[i].b] = dis[pos] + con[i].v; dfs(con[i].b,pos); dfn[++dcnt] = pos; } } typedef pair<int,int> pii; pii t[N << 2]; int ask(int x,int y) { return dis[x] + dis[y] - 2 * dis[lca(x,y)]; } void merge(pii& x,pii ls,pii rs) { static int rec[4]; rec[0] = ls.first; rec[1] = ls.second; rec[2] = rs.first; rec[3] = rs.second; x = pii(-1,-1); int cur = -1, tmp; for (int i = 0 ; i < 4 ; ++ i) for (int j = i+1 ; j < 4 ; ++ j) { if (rec[i] == -1 || rec[j] == -1) continue; tmp = ask(rec[i],rec[j]); if (tmp > cur) x = pii(rec[i],rec[j]), cur = tmp; } } void build(int x=1,int lp=1,int rp=n) { if (lp == rp) return (void) (t[x] = pii(lp,-1)); int mid = (lp + rp) >> 1; build(x<<1,lp,mid); build(x<<1|1,mid+1,rp); merge(t[x],t[x<<1],t[x<<1|1]); } pii query(int l,int r,int x=1,int lp=1,int rp=n) { if (l > rp || lp > r) return pii(-1,-1); if (lp >= l && rp <= r) return t[x]; int mid = (lp + rp) >> 1; pii ret; merge(ret,query(l,r,x<<1,lp,mid),query(l,r,x<<1|1,mid+1,rp)); return ret; } int main() { int x,y,z,a,b,c,d; read(n); for (int i = 1 ; i < n ; ++ i) { read(x), read(y), read(z); add(x,y,z); add(y,x,z); } dfs(1,0); for (int i = 1 ; i <= dcnt ; ++ i) { mn[i][0] = i; for (int j = 1 ; (1 << j) <= i ; ++ j) mn[i][j] = dep[dfn[mn[i][j-1]]] < dep[dfn[mn[i - (1 << j >> 1)][j-1]]] ? mn[i][j-1] : mn[i - (1 << j >> 1)][j-1]; } for (int i = 2 ; i <= dcnt ; i <<= 1) ++ ln[i]; for (int i = 2 ; i <= dcnt ; ++ i) ln[i] += ln[i-1]; build(); read(m); for (int i = 1 ; i <= m ; ++ i) { read(a), read(b), read(c), read(d); pii t1 = query(a,b); pii t2 = query(c,d); printf("%d\n",max(max(ask(t1.first,t2.first),ask(t1.first,t2.second)),max(ask(t1.second,t2.first),ask(t1.second,t2.second)))); } return 0; }
小結:直徑這個東西的性質還是很豐富的。通過利用點集直徑的可合併性,很容易套上一些資料結構。同時,兩個點集間的最遠點對可以轉化為求各自的直徑,這就使回答多個集合間的最遠點對異常簡潔。