最遠點對 [線段樹+樹的直徑]
最遠點對(線段樹+樹的直徑)
題目
\(n\) 個點被 \(n-1\) 條邊連線成了一顆樹,給出 \([a,b]\) 和 \([c,d]\) 兩個區間,表示點的標號請你求出兩個區間內各選一點之間的最大距離,即你需要求出\(max\{dis(i,j)\ |\ a\leqslant i\leqslant b,c\leqslant j\leqslant d\}\)
(\(PS\): 建議使用讀入優化)
輸出格式
第一行一個數字 \(n\leqslant 100000\)。
第二行到第 \(n\) 行每行三個數字描述路的情況, \(x,y,z\) \((1\leqslant x,y\leqslant n,1\leqslant z\leqslant 10000)\)
第\(n+1\)行一個數字\(m\),表示詢問次數 \(m\leqslant 100000\)。
接下來\(m\)行,每行四個數\(a,b,c,d\)。
輸出格式
共 \(m\) 行,表示每次詢問的最遠距離
樣例
樣例輸入
5
1 2 1
2 3 2
1 4 3
4 5 4
1
2 3 4 5
樣例輸出
10
資料範圍與提示
對於\(20\%\)的資料,保證\(n,m\leqslant 300\)。
對於另外\(20\%\)的資料,保證\(b-a,d-c\leqslant 100,m\leqslant 200\)。
對於另外\(20\%\)的資料,保證給出的樹為一條鏈。
分析
一道超級好(噁心) 的 碼農(思維)題。
題意就是給你一棵樹,給你 \(T\) 對區間,找到兩兩區間中的兩個點之間的最遠距離。
暴力的話就是列舉兩個區間中的點,然後找到最大的距離 \(dis\) ,這樣你就會獲得 \(TLE\ 0\) 分的好成績。我們考慮一下如何維護所有區間中的最大距離,從而在每一次詢問的時候可以直接查詢到兩個區間中最長的一段的兩個端點和最長的端點之間的距離。
因為這裡詢問的時候給的是區間的形式,那麼能夠想到的就是用線段樹來維護區間中的最值。而這裡只需要找到最大值的兩個端點即可,並不需要記錄最值,那麼我們的 \(push\ up\) 函式就需要一些改動。
在建樹的時候,我們按照節點編號來進行建樹,在到達葉子節點的時候,讓當前的最大值的端點都是 \(l\)
每個父親節點的位元組點都是儲存著最大距離的左端點和右端點的,那麼這裡是有 \(6\) 種組合方式,我們根據這 \(6\) 種之中的最大距離的兩個點來更新父親的最大距離的左端點和右端點。進行六次比較即可,求距離可以用 \(LCA\) 來求解。
程式碼
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 5e5+10;
int top[maxn],siz[maxn],son[maxn],fa[maxn];
int dis[maxn],dep[maxn];
struct Node{//L和R儲存最大距離的u左端點和右端點。
int l,r,L,R;
}t[maxn];
struct N{//建邊結構體
int v,next,val;
}e[maxn];
int head[maxn],tot;
inline int read(){//快讀
int s = 0,f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-')f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
s = s * 10 + ch - '0';
ch = getchar();
}
return s * f;
}
inline void Add(int x,int y,int z){//建邊
e[++tot].v = y;
e[tot].val = z;
e[tot].next = head[x];
head[x] = tot;
}
inline void dfs1(int x,int f){//樹剖第一遍dfs
siz[x] = 1;
dep[x] = dep[fa[x]] + 1;
for(register int i=head[x];i;i=e[i].next){
int v = e[i].v;
if(v == f)continue;
fa[v] = x;
dis[v] = dis[x] + e[i].val;//求出每個節點距離根的距離
dfs1(v,x);
siz[x] += siz[v];
if(!son[x] || siz[son[x]] < siz[v]){
son[x] = v;
}
}
}
inline void dfs2(int x,int topf){//第二遍dfs
top[x] = topf;
if(son[x])dfs2(son[x],topf);
for(register int i=head[x];i;i=e[i].next){
int v = e[i].v;
if(v != fa[x] && v != son[x])dfs2(v,v);
}
}
inline int lca(int x,int y){//求lca
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])swap(x,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
inline int getdis(int x,int y){//求兩點之間的距離
int pre = lca(x,y);
return dis[x] + dis[y] - 2 * dis[pre];
}
inline void Merge(int a1,int a2,int b1,int b2,int &c1,int &c2){//將兒子節點的四個端點更新為父親節點的最大距離的兩個端點。
int ans = 0;
//下邊記錄的是六種端點情況
int a = getdis(a1,a2);
int b = getdis(a1,b1);
int c = getdis(a2,b1);
int d = getdis(a2,b2);
int e = getdis(a1,b2);
int f = getdis(b1,b2);
//以下依次找到最大距離的兩個端點,通過取地址直接賦值
if(a > ans){ans = a;c1 = a1;c2 = a2;}
if(b > ans){ans = b;c1 = a1;c2 = b1;}
if(c > ans){ans = c;c1 = a2;c2 = b1;}
if(d > ans){ans = d;c1 = a2;c2 = b2;}
if(e > ans){ans = e;c1 = a1;c2 = b2;}
if(f > ans){ans = f;c1 = b1;c2 = b2;}
}
void pushup(int rt){//pushup函式更新父親節點的最大距離左右端點
Merge(t[rt<<1].L,t[rt<<1].R,t[rt<<1|1].L,t[rt<<1|1].R,t[rt].L,t[rt].R);
}
void build(int rt,int l,int r){//建立線段樹
t[rt].l = l;
t[rt].r = r;
if(l == r){t[rt].L = l;t[rt].R = l;return;}
int mid = (l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
void query(int rt,int l,int r,int &L,int &R){//查詢區間中最大距離的左右端點,利用取地址直接賦值
if(t[rt].l >= l && t[rt].r <= r){
Merge(L,R,t[rt].L,t[rt].R,L,R);
return;
}
int mid = t[rt].l + t[rt].r >> 1;
if(l <= mid)query(rt<<1,l,r,L,R);
if(r > mid)query(rt<<1|1,l,r,L,R);
}
int main(){
int n = read(),m;
for(register int i=1;i<n;++i){//建邊
int x = read(),y = read(),z = read();
Add(x,y,z);Add(y,x,z);
}
//樹剖處理
dfs1(1,0);
dfs2(1,1);
build(1,1,n);//建立線段樹
m = read();
while(m--){
int a = read(),b = read(),c = read(),d = read();
int e = a,f = b,g = c,h = d;//複製一下四個區間
query(1,a,b,e,f);//直接賦值為區間最大距離的左右端點
query(1,c,d,g,h);
int ans = 0;
//以下分別找到兩個區間之間的最大距離
ans = max(ans,max(getdis(e,g),getdis(e,h)));
ans = max(ans,max(getdis(f,g),getdis(f,h)));
printf("%d\n",ans);//輸出答案
}
return 0;
}