[51nod] 1766树上的最远点对 树的直径 树剖LCA+ST表静态查询

Posted 昵称很长很长真是太好了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[51nod] 1766树上的最远点对 树的直径 树剖LCA+ST表静态查询相关的知识,希望对你有一定的参考价值。

题意:
给你一棵带权树,q次查询,每次给出两个区间, [ l 1 , r 1 ] [ l 2 , r 2 ] [l_1,r_1] [l_2,r_2] [l1,r1][l2,r2]从这两个区间中分别选择两个数字,使得这两个点的距离最大,这个最大的距离是多少。
题解:
树的直径性质:距离树上任意点最远的点一定是直径的一端。此结论在点集中依然试用。
那么答案路径的两端一定是 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]直径的一端和 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]直径的一端的连线。
那么我们只要求出 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]的直径(两个端点+直径长度), [ l 2 , r 2 ] [l_2,r_2] [l2,r2]直径(两个端点+直径长度)
假设 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]的直径端点为a,b,假设 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]的直径端点为c,d,那么答案即为
m a x { a 到 c , a 到 d , b 到 c , b 到 d } max \\{a到c,a到d,b到c,b到d \\} max{ac,ad,bc,bd} 由上面的结论得出。

那么如何快速求出 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]的直径和 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]的直径。
既然我们已经有如上的结论了那么对于一个连续的区间,我们可以通过线段树来求,对于区间的网上递归合并。
因为此题为静态查询,那么线段树可以替换成ST表, O ( n l o g n ) O(nlogn) O(nlogn)预处理后 O ( 1 ) O(1) O(1)查询即可

代码:

#include<bits/stdc++.h>
#define endl '\\n'
using namespace std;

const int maxn=2e5+10;

struct node{
    int to,w,next;
}edge[maxn];
int head[maxn],cnt;

void add(int u,int v,int w){
    edge[cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

int fa[maxn],dis[maxn],siz[maxn],son[maxn],dep[maxn];

void dfs1(int x,int f){
    fa[x]=f;
    siz[x]=1;
    dep[x]=dep[f]+1;
    for(int i=head[x];~i;i=edge[i].next){
        int v=edge[i].to;
        int w=edge[i].w;
        if(v==f) continue;
        dis[v]=dis[x]+w;
        dfs1(v,x);
        siz[x]+=siz[v];
        if(siz[v]>siz[son[x]]) son[x]=v;
    }
}

int top[maxn];
void dfs2(int x,int t){
    top[x]=t;
    if(!son[x]) return ;
    dfs2(son[x],t);
    for(int i=head[x];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v==fa[x]||v==son[x]) continue;
        dfs2(v,v);
    }
}

int LCA(int x,int y){
    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;
}


struct Case{
    int x,y,len;
}a[maxn][23];
int mn[maxn];

inline int get_dis(int x,int y){
    int lca=LCA(x,y);
    return dis[x]+dis[y]-2*dis[lca];
}

Case stmer(Case x,Case y){
    Case res={0,0,max(x.len,y.len)};
    if(x.len>y.len) res=x;
    else res=y;
    int a=x.x,b=x.y,c=y.x,d=y.y;
    int now=get_dis(a,c);
    if(now>res.len) res={a,c,now};
    now=get_dis(a,d);
    if(now>res.len) res={a,d,now};
    now=get_dis(b,c);
    if(now>res.len) res={b,c,now};
    now=get_dis(b,d);
    if(now>res.len) res={b,d,now};
    return res;
}
void init(int n){
    mn[0]=-1;
    for(int i=1;i<=n;i++){
        mn[i]=mn[i>>1]+1;
    }
    for(int j=1;j<=mn[n];j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            a[i][j]=stmer(a[i][j-1],a[i+(1<<(j-1))][j-1]);
        }
    }
}

Case query(int l,int r){
    int k=mn[r-l+1];
    return stmer(a[l][k],a[r-(1<<k)+1][k]);
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    memset(head,-1,sizeof head);
    int n;
    cin>>n;
    for(int i=1;i<n;i++){
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,z);
        a[i][0].x=a[i][0].y=i;
    }
    a[n][0].x=a[n][0].y=n;
    dfs1(1,0);

    dfs2(1,1);
    init(n);
    int q;
    cin>>q;
    while(q--){
        int x,y,z,s;
        cin>>x>>y>>z>>s;
        Case xx=query(x,y);
        Case yy=query(z,s);
        Case res={0,0,0};
        int a=xx.x,b=xx.y,c=yy.x,d=yy.y;
        int now=get_dis(a,c);
        if(now>res.len) res={a,c,now};
        now=get_dis(a,d);
        if(now>res.len) res={a,d,now};
        now=get_dis(b,c);
        if(now>res.len) res={b,c,now};
        now=get_dis(b,d);
        if(now>res.len) res={b,d,now};
        cout<<res.len<<endl;
    }

}

以上是关于[51nod] 1766树上的最远点对 树的直径 树剖LCA+ST表静态查询的主要内容,如果未能解决你的问题,请参考以下文章

[51nod] 1766树上的最远点对 树的直径 树剖LCA+ST表静态查询

51nod 1766 树上的最远点对(线段树)

做题51Nod1766树上的最远点对——直径&线段树

[51nod] 1766 树上的最远点对

51Nod 1766 树上的最远点对

51nod1766 树上的最远点对