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


#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\n");
else if (num == 1) printf("0\n");
else printf("%d\n", p[1].maxdis);
}
}
return 0;
}

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. F4+2 团队项目软件设计方案

    目录 1引言 1.1编写目的 1.2背景 1.3定义 1.4参考资料 2总体设计 2.1需求规定 2.2运行环境 2.3基本设计概念和处理流程 2.4功能描述 2.5功能器求与程序的关系 2.6人工处 ...

  2. Oracle 起诉 Google 事件

    最近,Google 和 Oracle 纠缠多年的“Java 侵权案”又有了新的判决结果.Google 在此次对决中败诉,并可能需要支付高达88亿美元的赔偿金.这个案件还引发关于 API(应用程序编程接 ...

  3. linux使用flock文件锁

    使用linux flock 文件锁实现任务锁定,解决冲突 格式: flock [-sxun][-w #] fd# flock [-sxon][-w #] file [-c] command flock ...

  4. 20170714xlVba多个工作簿转多个Word文档表格

    Public Sub SameFolderGather() Application.ScreenUpdating = False Application.DisplayAlerts = False A ...

  5. Memory and Casinos CodeForces - 712E (概率,线段树)

    题目链接 题目大意:$n$个点, 每个点$i$有成功率$p_i$, 若成功走到$i+1$, 否则走到走到$i-1$, 多组询问, 求从$l$出发, 在$l$处不失败, 最后在$r$处胜利的概率 设$L ...

  6. qxx 项目总结

    一.账号池功能 1. 右边注释添加格式 private $client; // Jyeoo的接口 2. 将一些配置放到某个配置文件中,然后通过加载配置环境,来获取相关的变量 app()->con ...

  7. LeetCode 318. Maximum Product of Word Lengths (状态压缩)

    题目大意:给出一些字符串,找出两个不同的字符串之间长度之积的最大值,但要求这两个字符串之间不能拥有相同的字符.(字符只考虑小写字母). 题目分析:字符最多只有26个,因此每个字符串可以用一个二进制数来 ...

  8. Mybatis学习总结--------Mybatis <where>标签 (九)

      <select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WH ...

  9. Grep console 设置

    Grep console     DEBUG 9961B8 INFO 4B5E76 WARN 8A8A00 ERROR 9F6B00 8A7674      

  10. 编译EXE文件的时候pcap编译不进去。 pyinstaller pcap pypcap 报错

    如果生成的exe源码中有import pcap 那么你目标机上就要先装npcap 并勾选winpcap API. 然后就不出这个问题了. 暂时的办法是第一个exe不包含import pcap.自检np ...