zoukankan      html  css  js  c++  java
  • BZOJ1095: [ZJOI2007]Hide 捉迷藏【线段树维护括号序列】【思维好题】

    Description

      捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩
    捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋
    子都互相可达。游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的
    时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要
    求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两
    个孩子的距离(即最远的两个关灯房间的距离)。 我们将以如下形式定义每一种操作: C(hange) i 改变第i个房
    间的照明状态,若原来打开,则关闭;若原来关闭,则打开。 G(ame) 开始一次游戏,查询最远的两个关灯房间的
    距离。

    Input

      第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。接下来N-1行每行两个整数a, b,
    表示房间a与房间b之间有一条走廊相连。接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如
    上文所示。

    Output

      对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关
    着灯的,输出0;若所有房间的灯都开着,输出-1。

    Sample Input

    8
    1 2
    2 3
    3 4
    3 5
    3 6
    6 7
    6 8
    7
    G
    C 1
    G
    C 2
    G
    C 1
    G

    Sample Output

    4
    3
    3
    4

    HINT

    对于100%的数据, N ≤100000, M ≤500000。


    思路

    首先来了解一下括号序列怎么生成

    void dfs(int u, int fa) {
      c[++ind] = '(';
      c[++ind] = (char) (u + '0' - 1);
      for (int i = head[u]; i; i = E[i].nxt) {
        int v = E[i].v;
        if (v == fa) continue;
        dfs(v, u);
      }
      c[++ind] = ')';
    }
    

    这样就得到了一个括号序列

    比如样例中的序列就是((1(2(3(6(8)(7))(5)(4)))))

    然后如果把数字去掉,就发现这个序列变成了(((((()())()()))))

    其实可以简单理解为(()是向下,())是向上那么我们如何利用这个信息

    如果我们要计算两点的距离,先把这两点之间的括号序列提取出来

    比如说8和4,括号序列是()())()(),消除掉中间可以匹配的括号变成())()

    这就说明8到4只需要上行两次下行一次,至于中间的匹配括号实际上是一上一下全部抵消了

    所以我们就可以简单知道8和4的路径是3

    那么我们实际上需要求的是树上两个黑点之间的最长距离

    把化简过后的括号序列表示出来,(S(a,b))表示有a个())和b个(()组成的括号序列

    考虑怎么合并(S_1(a_1,b_1))(S_2(a_2,b_2))

    首先可以知道中间匹配掉的括号有(min(b_1,a_2))

    那么分情况讨论(接下来的所有讨论中都默认自动化简括号序列)

    1. 如果(a_2le b_1)(S(a_1-a_2+b_1,b_2),len=a_1+b_1-a_2+b_2)
    2. 反之,(S(a_1,b_2-b_1+a_2),len=a_1-b_1+a_2+b_2)

    1的左右两边的贡献拆开看,发现左边的贡献是(a_1+b_1),右边贡献是(-a_2+b_2)

    2的左右两边的贡献拆开看,发现左边的贡献是(a_1-b_1),右边贡献是(a_2+b_2)

    于是我们需要维护的就变成了

    • (l_{add}):区间所有前缀最大的(a+b)
    • (l_{sub}):区间所有前缀最大的(b - a)
    • (r_{add}):区间所有后缀最大的(a+b)
    • (r_{sub}):区间所有后缀最大的(a-b)

    同时记录(l_{len})表示区间左边一部分括号的长度,(r_{len})表示区间右边一部分括号的长度

    (maxdis)是区间中的左右括号的最大长度(答案)

    先倒着看怎么更新(maxdis),分成左右两边和跨过中间的两部分来算

    [maxdis=max(ld.maxdis,rd.maxdis,ld.r_{add}+rd.l_{sub},ld.r_{sub}+rd.l_{add}) ]

    后面的两个式子就是根据上面推导出来的式子进行更新的

    然后看一看(l_{len})(r_{len}),更新比较简单,直接分情况进行讨论就可以了

    1. 如果(ld.r_{len} >= rd.l_{len})

    [t.l_{len} = ld.l_{len} \ t.r_{len} = rd.r_{len} + ld.r_{len} - rd.l_{len} ]

    1. 反之

    [t.l_{len} = ld.l_{len} + rd.l_{len} - ld.r_{len} \ t.r_{len} = rd.r_{len} ]

    接下来就是对(l_{add},l_{sub},r_{add},r_{sub})的更新了

    在这里为了避免冗余的描述,默认情况1是ld用来合并的右区间长度大于等于rd用来合并的左区间长度,2是相反的情况

    同时一切都在

    1. (S(a_1,b_1 + b_2-a_2))
    2. (S(a_1+a_2-b_1,b_2))

    的情况上展开叙述

    • 更新(l_{add})

      1. (a+b=a_1+b_1-a_2+b_2=ld.l_{len} + ld.r_{len} + rd.l_{sub})

      2. (a+b=a_1-b_1+a_2+b_2=ld.l_{len} - ld.r_{len} + rd.l_{add})

      所以总的式子是:

    [l_{add} = max(ld.l_{add}, ld.l_{len} + ld.r_{len} + rd.l_{sub},ld.l_{len} - ld.r_{len} + rd.l_{add}) ]

    • 更新(l_{sub})

      因为经过推导发现,无论是1还是2,最后表示出来的(b-a)都是(-a_1+b_1-a_2+b_2=- ld.l_{len}+ ld.r_{len} + rd.l_{sub})

      所以式子是:

      [l_{sub} = max(ld.l_{sub}, - ld.l_{len}+ ld.r_{len} + rd.l_{sub}) ]

    • 更新(r_{add})

      1. (a+b=a_1+b_1-a_2+b_2=ld.r_{add}-rd.l_{len} + rd.r_{len})
      2. (a+b=a_1-b_1+a_2+b_2=ld.r_{sub}+rd.l_{len}+rd.r_{len})

      总的式子:

      [r_{add} = max(rd.r_{add}, ld.r_{add} - rd.l_{len} + rd.r_{len}, ld.r_{sub} + rd.l_{len} + rd.r_{len}) ]

    • 更新(r_{sub})

      发现无论最后表示出来的式子一定是(a-b=a_1-b_1+a_2-b_2=ld.r_{sub} + rd.l_{len} - rd.r_{len})

      总的式子是:

      [r_{sub} = max(rd.r_{sub}, ld.r_{sub} + rd.l_{len} - rd.r_{len}) ]

    我们现在知道怎么维护括号序列了,但是还有一个问题,就是(r_{len},l_{add},l_{sub})的右端点,(l_{len},r_{add},r_{sub})的左端点都需要有黑点才成立

    那么还是偷懒直接把点带进去维护好了

    于是初值就可以这样设定,为了满足端点必须有黑点的限制,我们只在当前节点代表一个黑点的时候把(l_{add},l_{sub},r_{add},r_{sub})赋值成(0),否则就把这四个值都设成$-infty $

    然后如果当前节点是右括号,就把(l_{len})设成1,

    如果当前节点是左括号,就把(r_{len})设成1就可以了

    完结撒花


    总结

    很好的一道题,就是pushup函数写起来有些自闭,用括号序列压缩的方法把树的形态表示了出来,非常的巧妙,而且比动态点分治的做法快了许多,写的时候就是写初始化函数的时候没有引用,导致出现了一系列问题,下次注意好了,这道题的思维方式还是很值得借鉴的


    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int INF_of_int = 1e9;
    const int N = 3e5 + 10;
    const int M = N << 2;
    
    struct Edge {
      int v, nxt;
    } E[N << 1];
    int head[N], tot = 0;
    int n, q, num = 0, ind = 0, pre[N], typ[N], col[N], dfn[N];
    char c[10];
    
    //typ -1:')' 1:'(' 0:col
    
    void addedge(int u, int v) {
      E[++tot] = (Edge) {v, head[u]};
      head[u] = tot;
    }
    
    void dfs(int u, int fa) {
      typ[++ind] = 1;
      typ[++ind] = 0;
      dfn[u] = ind;
      pre[ind] = u;
      col[u] = 1;
      for (int i = head[u]; i; i = E[i].nxt) {
        int v = E[i].v;
        if (v == fa) continue;
        dfs(v, u);
      }
      typ[++ind] = -1;
    }
    
    #define LD (t << 1)
    #define RD (t << 1 | 1)
    
    struct Node {
      int maxdis, l_len, r_len;
      int l_add, r_add, l_sub, r_sub;
    } p[M];
    
    void init(Node &t, int pos) {
      t.maxdis = -INF_of_int;
      t.l_len = t.r_len = 0;
      if (typ[pos] != 0) {
        if (typ[pos] > 0) t.r_len = 1;
        if (typ[pos] < 0) t.l_len = 1;
        t.l_add = t.r_add = t.l_sub = t.r_sub = -INF_of_int;
      } else {
        if (col[pre[pos]]) t.l_add = t.r_add = t.l_sub = t.r_sub = 0;
        else t.l_add = t.r_add = t.l_sub = t.r_sub = -INF_of_int;
      }
    }
    
    Node pushup(Node ld, Node rd) {
      Node t;
      t.maxdis = max(max(ld.maxdis, rd.maxdis), max(ld.r_add + rd.l_sub, ld.r_sub + rd.l_add));
      t.l_add = max(ld.l_add, max(ld.l_len - ld.r_len + rd.l_add, ld.l_len + ld.r_len + rd.l_sub));
      t.l_sub = max(ld.l_sub, ld.r_len - ld.l_len + rd.l_sub);
      t.r_add = max(rd.r_add, max(ld.r_add - rd.l_len + rd.r_len, ld.r_sub + rd.l_len + rd.r_len));
      t.r_sub = max(rd.r_sub, ld.r_sub + rd.l_len - rd.r_len);
      if (ld.r_len >= rd.l_len) t.l_len = ld.l_len, t.r_len = rd.r_len + ld.r_len - rd.l_len;
      else t.l_len = ld.l_len + rd.l_len - ld.r_len, t.r_len = rd.r_len; 
      return t;
    }
    
    void build(int t, int l, int r) {
      if (l == r) {
        init(p[t], l);
        return;
      }
      int mid = (l + r) >> 1;
      build(LD, l, mid);
      build(RD, mid + 1, r);
      p[t] = pushup(p[LD], p[RD]);
    }
    
    void modify(int t, int l, int r, int pos) {
      if (l == r) {
        init(p[t], l);
        return;
      }
      int mid = (l + r) >> 1;
      if (pos <= mid) modify(LD, l, mid, pos);
      else modify(RD, mid + 1, r, pos);
      p[t] = pushup(p[LD], p[RD]);
    }
    
    int main() {
    #ifdef dream_maker
      freopen("input.txt", "r", stdin);
    #endif
      scanf("%d", &n);
      for (int i = 2; i <= n; i++) {
        int u, v; scanf("%d %d", &u, &v);
        addedge(u, v);
        addedge(v, u);
      }
      dfs(1, 0);
      num = n;
      build(1, 1, ind);
      scanf("%d", &q);
      while (q--) {
        scanf("%s", c);
        if (c[0] == 'C') {
          int u; scanf("%d", &u);
          if (col[u]) --num;
          else ++num;
          col[u] ^= 1;
          modify(1, 1, ind, dfn[u]);
        } else {
          if (num == 0) printf("-1
    ");
          else if (num == 1) printf("0
    ");
          else printf("%d
    ", p[1].maxdis);
        }
      }
      return 0;
    }
    
  • 相关阅读:
    Java基础01-JVM内存分析
    性能测试工具LoadRunner32-LR之windows性能监控Perfmon
    性能测试工具LoadRunner31-LR之链接mysql
    性能测试工具LoadRunner30-LR之监控Tomcat
    性能测试工具LoadRunner29-LR之测试java代码
    JS活动倒计时案例
    鼠标图片跟随案例
    如何实现网页PC端自动跳转到手机移动端(备用)
    JavaScript—12高级事件
    JavaScript—11 DOM基础的核心要点整理
  • 原文地址:https://www.cnblogs.com/dream-maker-yk/p/10061736.html
Copyright © 2011-2022 走看看