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。


思路

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

  1. void dfs(int u, int fa) {
  2. c[++ind] = '(';
  3. c[++ind] = (char) (u + '0' - 1);
  4. for (int i = head[u]; i; i = E[i].nxt) {
  5. int v = E[i].v;
  6. if (v == fa) continue;
  7. dfs(v, u);
  8. }
  9. c[++ind] = ')';
  10. }

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

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


  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int INF_of_int = 1e9;
  4. const int N = 3e5 + 10;
  5. const int M = N << 2;
  6. struct Edge {
  7. int v, nxt;
  8. } E[N << 1];
  9. int head[N], tot = 0;
  10. int n, q, num = 0, ind = 0, pre[N], typ[N], col[N], dfn[N];
  11. char c[10];
  12. //typ -1:')' 1:'(' 0:col
  13. void addedge(int u, int v) {
  14. E[++tot] = (Edge) {v, head[u]};
  15. head[u] = tot;
  16. }
  17. void dfs(int u, int fa) {
  18. typ[++ind] = 1;
  19. typ[++ind] = 0;
  20. dfn[u] = ind;
  21. pre[ind] = u;
  22. col[u] = 1;
  23. for (int i = head[u]; i; i = E[i].nxt) {
  24. int v = E[i].v;
  25. if (v == fa) continue;
  26. dfs(v, u);
  27. }
  28. typ[++ind] = -1;
  29. }
  30. #define LD (t << 1)
  31. #define RD (t << 1 | 1)
  32. struct Node {
  33. int maxdis, l_len, r_len;
  34. int l_add, r_add, l_sub, r_sub;
  35. } p[M];
  36. void init(Node &t, int pos) {
  37. t.maxdis = -INF_of_int;
  38. t.l_len = t.r_len = 0;
  39. if (typ[pos] != 0) {
  40. if (typ[pos] > 0) t.r_len = 1;
  41. if (typ[pos] < 0) t.l_len = 1;
  42. t.l_add = t.r_add = t.l_sub = t.r_sub = -INF_of_int;
  43. } else {
  44. if (col[pre[pos]]) t.l_add = t.r_add = t.l_sub = t.r_sub = 0;
  45. else t.l_add = t.r_add = t.l_sub = t.r_sub = -INF_of_int;
  46. }
  47. }
  48. Node pushup(Node ld, Node rd) {
  49. Node t;
  50. t.maxdis = max(max(ld.maxdis, rd.maxdis), max(ld.r_add + rd.l_sub, ld.r_sub + rd.l_add));
  51. 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));
  52. t.l_sub = max(ld.l_sub, ld.r_len - ld.l_len + rd.l_sub);
  53. 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));
  54. t.r_sub = max(rd.r_sub, ld.r_sub + rd.l_len - rd.r_len);
  55. 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;
  56. else t.l_len = ld.l_len + rd.l_len - ld.r_len, t.r_len = rd.r_len;
  57. return t;
  58. }
  59. void build(int t, int l, int r) {
  60. if (l == r) {
  61. init(p[t], l);
  62. return;
  63. }
  64. int mid = (l + r) >> 1;
  65. build(LD, l, mid);
  66. build(RD, mid + 1, r);
  67. p[t] = pushup(p[LD], p[RD]);
  68. }
  69. void modify(int t, int l, int r, int pos) {
  70. if (l == r) {
  71. init(p[t], l);
  72. return;
  73. }
  74. int mid = (l + r) >> 1;
  75. if (pos <= mid) modify(LD, l, mid, pos);
  76. else modify(RD, mid + 1, r, pos);
  77. p[t] = pushup(p[LD], p[RD]);
  78. }
  79. int main() {
  80. #ifdef dream_maker
  81. freopen("input.txt", "r", stdin);
  82. #endif
  83. scanf("%d", &n);
  84. for (int i = 2; i <= n; i++) {
  85. int u, v; scanf("%d %d", &u, &v);
  86. addedge(u, v);
  87. addedge(v, u);
  88. }
  89. dfs(1, 0);
  90. num = n;
  91. build(1, 1, ind);
  92. scanf("%d", &q);
  93. while (q--) {
  94. scanf("%s", c);
  95. if (c[0] == 'C') {
  96. int u; scanf("%d", &u);
  97. if (col[u]) --num;
  98. else ++num;
  99. col[u] ^= 1;
  100. modify(1, 1, ind, dfn[u]);
  101. } else {
  102. if (num == 0) printf("-1\n");
  103. else if (num == 1) printf("0\n");
  104. else printf("%d\n", p[1].maxdis);
  105. }
  106. }
  107. return 0;
  108. }

BZOJ1095: [ZJOI2007]Hide 捉迷藏【线段树维护括号序列】【思维好题】的更多相关文章

  1. bzoj1095: [ZJOI2007]Hide 捉迷藏 线段树维护括号序列 点分治 链分治

    这题真是十分难写啊 不管是点分治还是括号序列都有一堆细节.. 点分治:时空复杂度$O(n\log^2n)$,常数巨大 主要就是3个堆的初始状态 C堆:每个节点一个,为子树中的点到它父亲的距离的堆. B ...

  2. BZOJ 1095: [ZJOI2007]Hide 捉迷藏(线段树维护括号序列)

    这个嘛= =链剖貌似可行,不过好像代码长度很长,懒得打(其实是自己太弱了QAQ)百度了一下才知道有一种高大上的叫括号序列的东西= = 岛娘真是太厉害了,先丢链接:http://www.shuizilo ...

  3. [bzoj1095][ZJOI2007]Hide 捉迷藏——线段树+括号序列

    题目大意 给定一棵所有点初始值为黑的无权树,你需要支援两种操作: 把一个点的颜色反转 统计最远黑色点对. 题解 本题是一个树上的结构.对于树上的结构,我们可以采用点分治.树链剖分等方法处理,这个题用了 ...

  4. BZOJ 1095 捉迷藏(线段树维护括号序列)

    对于树的一个括号序列,树上两点的距离就是在括号序列中两点之间的括号匹配完之后的括号数... 由此可以得出线段树的做法.. #include<cstdio> #include<iost ...

  5. [BZOJ 1095] [ZJOI2007]Hide 捉迷藏——线段树+括号序列(强..)

    神做法-%dalao,写的超详细 konjac的博客. 如果觉得上面链接的代码不够优秀好看,欢迎回来看本蒟蒻代码- CODE WITH ANNOTATION 代码中−6-6−6表示左括号'[',用−9 ...

  6. BZOJ1095: [ZJOI2007]Hide 捉迷藏【动态点分治】

    Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩 捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条 ...

  7. bzoj千题计划252:bzoj1095: [ZJOI2007]Hide 捉迷藏

    http://www.lydsy.com/JudgeOnline/problem.php?id=1095 点分树+堆 请去看 http://www.cnblogs.com/TheRoadToTheGo ...

  8. 动态点分治:Bzoj1095: [ZJOI2007]Hide 捉迷藏

    简介 这是我自己的一点理解,可能写的不好 点分治都学过吧.. 点分治每次找重心把树重新按重心的深度重建成了一棵新的树,称为分治树 这个树最多有log层... 动态点分治:记录下每个重心的上一层重心,这 ...

  9. BZOJ1095 [ZJOI2007] Hide 捉迷藏 (括号序列 + 线段树)

    题意 给你一颗有 \(n\) 个点的树 , 共有 \(m\) 次操作 有两种类别qwq 将树上一个点染黑/白; 询问树上最远的两个黑点的距离. \((n \le 200000, m ≤500000)\ ...

随机推荐

  1. scrapy 也能爬取妹子图?

    目录 前言 Media Pipeline 启用Media Pipeline 使用 ImgPipeline 抓取妹子图 瞎比比前言 我们在抓取数据的过程中,除了要抓取文本数据之外,当然也会有抓取图片的需 ...

  2. php5.4 的 arm 交叉编译

    ./configure --prefix=/h1root/usr/php --host=arm-linux --enable-libxml --with-mysql=mysqlnd --with-my ...

  3. java后台读取/解析 excel表格

    需求描述 前台需要上传excel表格,提交到后台,后台解析并返回给前台,展示在前台页面上! 前台部分代码与界面 <th style="padding: 7px 1px;width:15 ...

  4. 快速搭建一个简易的KMS 服务

    xu言: 之前,闹的沸沸扬扬的KMS激活工具自身都存在问题的事.让我们对以前的什么小马激活.kms激活.各种激活工具都去打了一个深深的“?”,到底哪些能用.哪些不能用.有些还注明的里面必须要关闭杀毒软 ...

  5. Confluence 6 更新目录

    当编辑目录时候的限制 你不能对你用户属于的目录进行编辑,禁用或者删除.这个能够预防管理员通过修改目录的时候讲自己属于的管理员权限从系统管理员组中删除. 这个限制对所有的用户目录类型适用.例如: 如果当 ...

  6. AND Graph CodeForces - 987F (状压)

    链接 大意:给定$m$个数, 若$x\&y=0$, 则在$x$与$y$之间连一条无向边. 求无向图的连通块个数 暴力连边显然超时的, 可以通过辅助结点优化连边, 复杂度$O(n2^n)$ #i ...

  7. Laravel JsonResponse数组获取

    有一个JsonResponse数据的格式如下: object(Illuminate\Http\JsonResponse)[474] protected 'data' => string '{&q ...

  8. java 判断字符串IP合法性以及获取IP的数值形式

    /** * 计算传入的IP地址的数字IP*/ public static long getIpNum(String ip) { long ipNum = 0; if (StringUtils.isNo ...

  9. 一个SQL调优/优化(SQL TUNING)“小把戏”“哄得”小朋友挺满意

    前几天,去一个用户那里,解决完问题,和一个小朋友闲聊,他有点愁眉不展.郁郁寡欢的样子,似乎没心情和我说话,之前,他的话是最多的,见此状,我就问:怎么了?小朋友?,他说,这几天应用人员说他的某个模块的性 ...

  10. SSO-基本概念

    什么是单点登录 单点登录(Single Sign On) 简称为sso,是目前流行的企业业务整合的解决方案之一.SSO的定义是在多个引用系统中用户只需要登录一次就可以访问所有相互信任的应用系统. 单点 ...