51Nod.1766.树上最远点对(树的直径 RMQ 线段树/ST表)

题目链接

\(Description\)

给定一棵树。每次询问给定\(a\sim b,c\sim d\)两个下标区间,从这两个区间中各取一个点,使得这两个点距离最远。输出最远距离。
\(n,q\leq10^5\)。

\(Solution\)

一个集合直径的两端点,在被划分为两个集合后一定是两个集合直径的四个端点中的两个。
即假设将\(S\)分为两个集合后,另外两个集合的直径的两端点分别为a,b和c,d,那么\(S\)集合的直径的两端点一定是a,b,c,d中的两个。
证明类似树的直径。
所以信息可以合并,所以就可以线段树啦。而且没有修改,ST表就够啦。

原来是两个区间各选一点。。=-=
写namespace不想改了...有点丑不要介意。

2333



ST表:

//500ms 44,948KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#define BIT 17//2^{17}=131072
//#define gc() getchar()
#define MAXIN 500000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=2e5+5;//2n

char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
namespace PRE
{
    int Enum,H[N>>1],nxt[N],to[N],len[N],dis[N>>1],pos[N>>1],Log2[N],st[N][BIT+1];
    inline void AE(int w,int u,int v)
    {
        to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
        to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, len[Enum]=w;
    }
    inline int LCA_dis(int l,int r)
    {
        if(l>r) std::swap(l,r);
        int k=Log2[r-l+1];
        return std::min(st[l][k],st[r-(1<<k)+1][k])<<1;
//      return dis[ref[std::min(st[l][k],st[r-(1<<k)+1][k])]]<<1;
    }
    inline int Dis(int x,int y)
    {
        return dis[x]+dis[y]-LCA_dis(pos[x],pos[y]);
    }
    void DFS(int x,int fa)
    {
        static int tot=0;
        st[pos[x]=++tot][0]=dis[x];//边权为正的话可以直接用dis[x]
        for(int i=H[x],v; i; i=nxt[i])
            if((v=to[i])!=fa) dis[v]=dis[x]+len[i], DFS(v,x), st[++tot][0]=dis[x];
    }
    void Init_RMQ(const int n)
    {
        for(int i=2; i<=n; ++i) Log2[i]=Log2[i>>1]+1;
        for(int j=1; j<=Log2[n]; ++j)
            for(int t=1<<j-1,i=n-t; i; --i)
                st[i][j]=std::min(st[i][j-1],st[i+t][j-1]);
    }
}
namespace SOL
{
    struct Node
    {
        int x,y;
    }A[N>>1][BIT];
    using PRE::Log2;
    Node Merge(const Node &a,const Node &b)
    {
        int x=a.x,y=a.y,X=b.x,Y=b.y,tx=x,ty=y,tmx=PRE::Dis(x,y),tmp;
        if((tmp=PRE::Dis(X,Y))>tmx) tmx=tmp,tx=X,ty=Y;
        if((tmp=PRE::Dis(x,X))>tmx) tmx=tmp,tx=x,ty=X;
        if((tmp=PRE::Dis(x,Y))>tmx) tmx=tmp,tx=x,ty=Y;
        if((tmp=PRE::Dis(y,X))>tmx) tmx=tmp,tx=y,ty=X;
        if((tmp=PRE::Dis(y,Y))>tmx) tmx=tmp,tx=y,ty=Y;
        return (Node){tx,ty};
    }
    inline Node Query(int l,int r)
    {
        int k=Log2[r-l+1];
        return Merge(A[l][k],A[r-(1<<k)+1][k]);
    }
    void Init_ST(const int n)
    {
        for(int i=1; i<=n; ++i) A[i][0]=(Node){i,i};
        for(int j=1; j<=Log2[n]; ++j)
            for(int t=1<<j-1,i=n-t; i; --i)
                A[i][j]=Merge(A[i][j-1],A[i+t][j-1]);
    }
    void Solve(const int n)
    {
        Init_ST(n);
        for(int Q=read(); Q--; )
        {
            int a=read(),b=read(),c=read(),d=read();
            Node X=Query(a,b),Y=Query(c,d);
            printf("%d\n",std::max(PRE::Dis(X.x,Y.x),std::max(PRE::Dis(X.x,Y.y),std::max(PRE::Dis(X.y,Y.x),PRE::Dis(X.y,Y.y)))));
        }
    }
}

int main()
{
    int n=read();
    for(int i=1; i<n; ++i) PRE::AE(read(),read(),read());
    PRE::DFS(1,1), PRE::Init_RMQ(2*n-1), SOL::Solve(n);

    return 0;
}


线段树:

//671ms 45,244KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#define BIT 17
#define gc() getchar()
#define MAXIN 100000
//#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=2e5+5;//2n

char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
namespace PRE
{
    int Enum,H[N>>1],nxt[N],to[N],len[N],dis[N>>1],pos[N>>1],Log2[N],st[N][BIT+1];
    inline void AE(int w,int u,int v)
    {
        to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
        to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, len[Enum]=w;
    }
    inline int LCA_dis(int l,int r)
    {
        if(l>r) std::swap(l,r);
        int k=Log2[r-l+1];
        return std::min(st[l][k],st[r-(1<<k)+1][k])<<1;
//      return dis[ref[std::min(st[l][k],st[r-(1<<k)+1][k])]]<<1;
    }
    inline int Dis(int x,int y)
    {
        return dis[x]+dis[y]-LCA_dis(pos[x],pos[y]);
    }
    void DFS(int x,int fa)
    {
        static int tot=0;
        st[pos[x]=++tot][0]=dis[x];//边权为正的话可以直接用dis[x]
        for(int i=H[x],v; i; i=nxt[i])
            if((v=to[i])!=fa) dis[v]=dis[x]+len[i], DFS(v,x), st[++tot][0]=dis[x];
    }
    void Init_RMQ(const int n)
    {
        for(int i=2; i<=n; ++i) Log2[i]=Log2[i>>1]+1;
        for(int j=1; j<=Log2[n]; ++j)
            for(int t=1<<j-1,i=n-t; i; --i)
                st[i][j]=std::min(st[i][j-1],st[i+t][j-1]);
    }
}
struct Segment_Tree
{
    #define ls rt<<1
    #define rs rt<<1|1
    #define lson l,m,rt<<1
    #define rson m+1,r,rt<<1|1
    #define S N<<1//2n
    int n,ansx,ansy,ansmx,X[S],Y[S],mxds[S];
    #undef S
    void Merge(int &x,int &y,int &mx,int X,int Y,int Mx)
    {
        int tmp,tx=x,ty=y,tmx=mx;
        if(Mx>tmx) tmx=Mx,tx=X,ty=Y;
        if((tmp=PRE::Dis(x,X))>tmx) tmx=tmp,tx=x,ty=X;
        if((tmp=PRE::Dis(x,Y))>tmx) tmx=tmp,tx=x,ty=Y;
        if((tmp=PRE::Dis(y,X))>tmx) tmx=tmp,tx=y,ty=X;
        if((tmp=PRE::Dis(y,Y))>tmx) tmx=tmp,tx=y,ty=Y;
        x=tx, y=ty, mx=tmx;
    }
    inline void Update(int rt)
    {
        int l=ls,r=rs;
        Merge(X[rt]=X[l],Y[rt]=Y[l],mxds[rt]=mxds[l],X[r],Y[r],mxds[r]);
    }
    void Build(int l,int r,int rt)
    {
        if(l==r) {X[rt]=Y[rt]=l; return;}
        int m=l+r>>1; Build(lson), Build(rson), Update(rt);
    }
    void Query(int l,int r,int rt,int L,int R)
    {
        if(L<=l && r<=R) {Merge(ansx,ansy,ansmx,X[rt],Y[rt],mxds[rt]); return;}
        int m=l+r>>1;
        if(L<=m) Query(lson,L,R);
        if(m<R) Query(rson,L,R);
    }
    void Solve()
    {
        int a=read(),b=read(),c=read(),d=read();
        ansx=a, ansy=a, ansmx=0;
        Query(1,n,1,a,b);
        int x1=ansx,y1=ansy; ansx=c, ansy=c, ansmx=0;
        Query(1,n,1,c,d);
        int x2=ansx,y2=ansy;
        printf("%d\n",std::max(PRE::Dis(x1,x2),std::max(PRE::Dis(x1,y2),std::max(PRE::Dis(y1,x2),PRE::Dis(y1,y2)))));
    }
}T;

int main()
{
    int n=read();
    for(int i=1; i<n; ++i) PRE::AE(read(),read(),read());
    PRE::DFS(1,1), PRE::Init_RMQ(2*n-1);
    T.n=n, T.Build(1,n,1);
    for(int Q=read(); Q--; T.Solve());

    return 0;
}

原文地址:https://www.cnblogs.com/SovietPower/p/10090344.html

时间: 2024-08-29 15:40:25

51Nod.1766.树上最远点对(树的直径 RMQ 线段树/ST表)的相关文章

【bzoj3065】带插入区间K小值 替罪羊树套权值线段树

题目描述 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少.这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问.这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少.这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问.(orz 主席

bzoj 1036 [ZJOI2008]树的统计Count(树链剖分,线段树)

1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 10677  Solved: 4313[Submit][Status][Discuss] Description 一 棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值

树状数组与线段树

一:树状数组 树状数组是对一个数组改变某个元素和求和比较实用的数据结构.两中操作都是O(logn). 需求:有时候我们需要频繁地求数组的前k项和或者求数组从小标i到j的和,这样每次最坏情况下的时间复杂度就会为O(N),这样效率太低了.而树状数组主要就是为了解决这样一个问题.树状数组在求和及修改都可以在O(lgN)时间内完成. 树状数组需要额外维护一个数组,我们设为C[N],原数组为A[N], 其中每个元素C[i]表示A[i-2^k+1]到A[i]的和,这里k是i在二进制时末尾0的个数.注意通过位

HDU - 3966 Aragorn&#39;s Story(树链剖分入门+线段树)

HDU - 3966 Aragorn's Story Time Limit: 3000MS   Memory Limit: 32768KB   64bit IO Format: %I64d & %I64u Submit Status Description Our protagonist is the handsome human prince Aragorn comes from The Lord of the Rings. One day Aragorn finds a lot of ene

可持久化专题(一)——浅谈主席树:可持久化线段树

前言 不得不说,可持久化数据结构真是太难了! 由于数据结构这东西真的太玄学了,学这个主席树我真的学了很久. 简介 主席树为什么叫主席树?据说因为它是一个名字缩写为\(HJT\)的神犇发明的,与当时主席的名字缩写一样...... 主席树实质上就是一棵可持久化线段树,它的具体实现可以看下面. 让我们从值域线段树开始说起 要学主席树,我们就要先学值域线段树. 值域线段树的区间存的并不是节点信息,而是在值在某一范围内的数的个数. 如图就是一棵值域线段树,其中1号节点存储的是大于等于1小于等于4的数字个数

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

poj 3237 Tree(树链剖分,线段树)

Tree Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 7268   Accepted: 1969 Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edges are numbered 1 through N − 1. Each edge is associated with

POJ3264 Balanced Lineup RMQ 线段树

求区间内最大数和最小数的差,用两棵线段树,一个维护区间最大值,一个维护区间最小值. #include <stdio.h> #include <vector> #include <math.h> #include <string.h> #include <string> #include <iostream> #include <queue> #include <list> #include <algori

bzoj 3626 [LNOI2014]LCA(离线处理+树链剖分,线段树)

3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1272  Solved: 451[Submit][Status][Discuss] Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1. 设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先. 有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[