zoukankan      html  css  js  c++  java
  • 【做题】51Nod1766树上的最远点对——直径&线段树

    原文链接 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 igcup B)的最远点对的两个点一定是(a,b,c,d)中的两个。

    至于证明,可以考虑直径的性质:到任何一个结点的最远点一定是两个直径端点中的一个。

    注:一个点集的最远点对可以通过构建虚树转化为直径。

    那么,考虑(A)中的一个结点,在(B)集合,到它最远的结点一定可以是(c,d)中的一个。否则,我们可以反证((c,d))不是(B)中的最远点对。那么,在(A igcup B)中,任何一个结点,到它的最远点一定是(a,b,c,d)中的一个。因此,最远点对就一定是(a,b,c,d)中的两个。

    于是,我们可以用线段树维护区间的最远点对。再根据我们的结论,([c,d])中到([a,b])的任意一点最远的结点一定是([c,d])里最远点对中的一点,因此我们求出([a,b])([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
    ",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;
    }
    

    小结:直径这个东西的性质还是很丰富的。通过利用点集直径的可合并性,很容易套上一些数据结构。同时,两个点集间的最远点对可以转化为求各自的直径,这就使回答多个集合间的最远点对异常简洁。

  • 相关阅读:
    Opengl绘制我们的小屋(二)第一人称漫游
    C# this.Invoke和this.BeginInvoke 最简单的写法
    C# 递归模型定义。赋值
    .net Core 2.1 后 Session保存,新页面获取不到值
    .net core mvc 错误信息显示 ModelState.AddModelError
    .net Core 依赖注入 Add********说明
    C# 中2个问号的作用。C#的??代表是什么意思
    asp.net mvc 加三层架构 完美搭配
    C# DataTable.Compute()用法
    C# DateTime判断时间
  • 原文地址:https://www.cnblogs.com/cly-none/p/9890837.html
Copyright © 2011-2022 走看看