bzoj 1095 捉迷藏(线段树)

题外话

最近课程不是很紧,准备按AC率版切bz,争取一天一道题以上。然后我喜闻乐见的发现之前剩下的题基本都是数据结构>_<。蛋疼啊。。。

Description

给定一棵树,每个节点要么是黑色,要么是白色,能执行两个操作:把某一个点取反色,返回距离最远的黑色点对。

Solution

这题看起来链分治,边分治都可做,然后搜到了小岛的题解。发现了逼格更高的做法,看了曹钦翔的《数据结构的提炼与压缩》,跪烂了。。。

这题用到了dfs序的性质,也就是括号序列。。

定义一种对一棵树的括号编码。这种编码方式很直观,所以,这里不给出严格的定义,用以下这棵树为例:

(图片自行脑补。。。)

可以先序遍历后写成:[A[B[E][F[H][I]]][C][D[G]]]

去掉字母后的串:[[[][[][]]][][[]]]

就称为这棵树的括号编码。(这个编码本质上是由深度优先遍历得到的)

考察两个结点,如 E 和 G ,

取出介于它们之间的那段括号编码 :][[][]]][][[

把匹配的括号去掉,得到:]][[

我们看到 2 个 ] 和 2 个 [,也就是说,在树中,从 E 向上爬 2 步,再向下走 2 步就到了 G。

注意到,题目中需要的信息只有这棵树中点与点的距离,所以,贮存编码中匹配的括号是没有意义的。

因此,对于介于两个节点间的一段括号编码 S,可以用一个二元组 (a, b) 描述它,即这段编码去掉匹配括号后有 a 个 ] 和 b 个 [。

所以,对于两个点 PQ,如果介于某两点 PQ 之间编码 S 可表示为 (a, b),PQ 之间的距离就是 a+b。

也就是说,题目只需要动态维护:max{a+b | S’(a, b) 是 S 的一个子串,且 S’ 介于两个黑点之间},

这里 S 是整棵树的括号编码。我们把这个量记为 dis(s)。

现在,如果可以通过左边一半的统计信息和右边一半的统计信息,得到整段编码的统计,这道题就可以用熟悉的线段树解决了。

这需要下面的分析。

考虑对于两段括号编码 S1(a1, b1) 和 S2(a2, b2),如果它们连接起来形成 S(a, b)。

注意到 S1、S2 相连时又形成了 min{b, c} 对成对的括号,合并后它们会被抵消掉。(?..这里 b, c 应该分别是指 b1 和 a2。。。

所以:

当 a2 < b1 时第一段 [ 就被消完了,两段 ] 连在一起,例如:

] ] [ [ + ] ] ] [ [ = ] ] ] [ [

当 a2 >= b1 时第二段 ] 就被消完了,两段 [ 连在一起,例如:

] ] [ [ [ + ] ] [ [ = ] ] [ [ [ (?..反了?。。。

这样,就得到了一个十分有用的结论:

当 a2 < b1 时,(a,b) = (a1-b1+a2, b2),

当 a2 >= b1 时,(a,b) = (a1, b1-a2+b2)。

由此,又得到几个简单的推论:

(i) a+b = a1+b2+|a2-b1| = max{(a1-b1)+(a2+b2), (a1+b1)+(b2-a2)}

(ii) a-b = a1-b1+a2-b2

(iii) b-a = b2-a2+b1-a1

由 (i) 式,可以发现,要维护 dis(s),就必须对子串维护以下四个量:

right_plus:max{a+b | S’(a,b) 是 S 的一个后缀,且 S’ 紧接在一个黑点之后}

right_minus:max{a-b | S’(a,b) 是 S 的一个后缀,且 S’ 紧接在一个黑点之后}

left_plus:max{a+b | S’(a,b) 是 S 的一个前缀,且有一个黑点紧接在 S 之后}

left_minus:max{b-a | S’(a,b) 是 S 的一个前缀,且有一个黑点紧接在 S 之后}

这样,对于 S = S1 + S2,其中 S1(a, b)、S2(c, d)、S(e, f),就有

(e, f) = b < c ? (a-b+c, d) : (a, b-c+d)

dis(S) = max{dis(S1), left_minus(S2)+right_plus(S1), left_plus(S2)+right_minus(S1), dis(S2)}

那么,增加这四个参数是否就够了呢?

是的,因为:

right_plus(S) = max{right_plus(S1)-c+d, right_minus(S1)+c+d, right_plus(S2)}

right_minus(S) = max{right_minus(S1)+c-d, right_minus(S2)}

left_plus(S) = max{left_plus(S2)-b+a, left_minus(S2)+b+a, left_plus(S1)}

left_minus(S) = max{left_minus(S2)+b-a, left_minus(S1)}

这样一来,就可以用线段树处理编码串了。实际实现的时候,在编码串中加进结点标号会更方便,对于底层结点,如果对应字符是一个括号或者一个白点,那 么right_plus、right_minus、left_plus、left_minus、dis 的值就都是 -maxlongint;如果对应字符是一个黑点,那么 right_plus、right_minus、left_plus、left_minus 都是 0,dis 是 -maxlongint。

现在这个题得到圆满解决,回顾这个过程,可以发现用一个串表达整棵树的信息是关键,这一“压”使得线段树这一强大工具得以利用…

然后这个题就做完了,太神了。。。

Code

#include <bits/stdc++.h>
#define ls (rt << 1)
#define rs (rt << 1 | 1)
using namespace std;
const int N = 100010, inf = 1e9;
int n, q, tot, cnt, dfn[N * 3], to[N << 1], nxt[N << 1], head[N], pos[N], cc[N];
struct Node {
    int l, r, l1, r1, l2, r2, c1, c2, dis;
    void init(int x) {
        dis = -inf;
        c1 = c2 = 0;
        if (dfn[x] == -2)   c2 = 1;//(
        if (dfn[x] == -5)   c1 = 1;//)
        if (dfn[x] > 0 && !cc[dfn[x]])  l1 = r1 = l2 = r2 = 0;
        else l1 = r1 = l2 = r2 = -inf;
    }
}a[N * 12];
inline int read(int &t) {
    int f = 1;char c;
    while (c = getchar(), c < ‘0‘ || c > ‘9‘) if (c == ‘-‘) f = -1;
    t = c - ‘0‘;
    while (c = getchar(), c >= ‘0‘ && c <= ‘9‘) t = t * 10 + c - ‘0‘;
    t *= f;
}
void add(int u, int v) {
    to[tot] = v, nxt[tot] = head[u], head[u] = tot++;
    to[tot] = u, nxt[tot] = head[v], head[v] = tot++;
}
void dfs(int u, int fa) {
    dfn[++cnt] = -2;//
    dfn[++cnt] = u;
    pos[u] = cnt;
    for (int i = head[u], v; ~i; i = nxt[i]) {
        v = to[i];
        if (v != fa)    dfs(v, u);
    }
    dfn[++cnt] = -5;//)
}
Node merge(Node a, Node b) {
    Node c;
    c.l = a.l, c.r = b.r;
    c.dis = max(a.dis, b.dis);
    c.dis = max(c.dis, max(a.r2 + b.l1, a.r1 + b.l2));
    if (b.c1 < a.c2)    c.c1 = a.c1, c.c2 = a.c2 - b.c1 + b.c2;
    else c.c1 = a.c1 + b.c1 - a.c2, c.c2 = b.c2;
    c.l1 = max(a.l1, max(b.l1 + a.c1 - a.c2, b.l2 + a.c1 + a.c2));
    c.r1 = max(b.r1, max(a.r1 - b.c1 + b.c2, a.r2 + b.c1 + b.c2));
    c.l2 = max(a.l2, b.l2 + a.c2 - a.c1);
    c.r2 = max(b.r2, a.r2 + b.c1 - b.c2);
    return c;
}
void change(int rt, int p) {
    if (a[rt].l == a[rt].r) {
        a[rt].init(a[rt].l);
        return;
    }
    int mid = a[rt].l + a[rt].r >> 1;
    if (p <= mid)   change(ls, p);
    else change(rs, p);
    a[rt] = merge(a[ls], a[rs]);
}
void build(int rt, int l, int r) {
    if (l == r) {
        a[rt].l = l, a[rt].r = r;
        a[rt].init(l);
        return;
    }
    int mid = l + r >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
    a[rt] = merge(a[ls], a[rs]);
}
int main() {
    memset(head, -1, sizeof(head));
    read(n);
    int now = n;
    for (int i = 1, x, y; i < n; ++i) {
        read(x), read(y);
        add(x, y);
    }
    dfs(1, 0);
    build(1, 1, cnt);
    read(q);
    while (q--) {
        char s[10];
        int x;
        scanf("%s", s);
        if (s[0] == ‘C‘) {
            read(x);
            if (!cc[x]) --now;
            else ++now;
            cc[x] ^= 1;
            change(1, pos[x]);
        }
        else {
            if (!now)   puts("-1");
            else if (now == 1)  puts("0");
            else printf("%d\n", a[1].dis);
        }
    }
    return 0;
}
时间: 2024-10-12 20:56:53

bzoj 1095 捉迷藏(线段树)的相关文章

【BZOJ 3476】 线段树===

59  懒惰的奶牛贝西所在的牧场,散落着 N 堆牧草,其中第 i 堆牧草在 ( Xi,Yi ) 的位置,数量有 Ai 个单位.贝西从家移动到某一堆牧草的时候,只能沿坐标轴朝正北.正东.正西.正南这四个方向移动,所以计算贝西和牧草间的距离时,应采用"曼哈顿距离"-- (x,y ) 和 (x ′,y ′) 之间的距离为|x ? x ′ | + |y ? y ′ |.例如贝西的家在 (0.5, 0.3),有一堆牧草在 (3, 2),那么它们之间的距离就是 4.2.贝西懒得走动,她想请你为它寻

bzoj 4627 值域线段树

4627: [BeiJing2016]回转寿司 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 523  Solved: 227[Submit][Status][Discuss] Description 酷爱日料的小Z经常光顾学校东门外的回转寿司店.在这里,一盘盘寿司通过传送带依次呈现在小Z眼前.不同的寿 司带给小Z的味觉感受是不一样的,我们定义小Z对每盘寿司都有一个满意度,例如小Z酷爱三文鱼,他对一盘三文 鱼寿司的满意度为10:小Z觉得金枪鱼没有

BZOJ 1067 降雨量 线段树

传送门 这题也是醉了 估计考点是特判 不是数据结构 考察if和else的运用 主要是考虑几种情况的分类 看代码吧 对了 我写的是线段树 因为我懒 懒得搞ST #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #define N 50000+5 #define M 200000+5 using namespace std; int year[N],a[

BZOJ 1230--lites 开关灯(线段树)

1230: [Usaco2008 Nov]lites 开关灯 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1682  Solved: 876[Submit][Status][Discuss] Description Farmer John尝试通过和奶牛们玩益智玩具来保持他的奶牛们思维敏捷. 其中一个大型玩具是牛栏中的灯. N (2 <= N <= 100,000) 头奶牛中的每一头被连续的编号为1..N, 站在一个彩色的灯下面.刚到傍晚的时候

BZOJ.3585.mex(线段树)

题目链接 考虑\([1,i]\)的\(mex[i]\),显然是单调的 而对于\([l,r]\)与\([l+1,r]\),如果\(nxt[a[l]]>r\),那么\([l+1,r]\)中所有\(>a[l]\)的数显然要改成\(a[l]\) 询问排序,离散化,预处理下nxt[],剩下就是线段树的区间更新.查询了 /* 离散化的时候>=n的全部看做n就好了 查询时是只需查r点的(l之前能更新r的已经更新完了,初始时是[1,r],r点现在就是[l,r]了) 单点即可不需要PushUp(也不好得某

BZOJ 3938 Robot 线段树

题目大意:给定n个点,每个点沿数轴匀速直线运动,多次改变某个点的速度和询问当前离数轴最远的点 标解见http://pan.baidu.com/share/link?shareid=4093182173&uk=2587171485#path=%252F%25E9%259B%2586%25E8%25AE%25AD%25E9%2598%259F%25E4%25BA%2592%25E6%25B5%258B%25202015%2520Round%2520%25231%2520%25E9%25A2%2598

【BZOJ1095】【ZJOI2007】Hide 捉迷藏 线段树维护括号序列 数据结构的压缩。

链接: #include <stdio.h> int main() { puts("转载请注明出处[vmurder]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/44829703"); } 题解: 首先由于此题太神以至于我其实还不会这道题,所以不妨介绍一下括号序列维护树构. 其实都是假的,就是一个点被扫到入栈的时候,序列加一个左括号,然后加入一个字符(可以不加),点出栈的时候就加一个右括号. 然

BZOJ 4530 LCT/线段树合并

//By SiriusRen #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=200050; int n,q,cnt,dfn[N],last[N],tree[N*16],lson[N*16],rson[N*16]; int first[N],next[N],v[N],w[N],tot,root[N],fa[N],deep[N],f[N];

BZOJ 3779 LCT 线段树 DFS序 坑

hhhh抄了半天lty代码最后T了  对拍也没事.. 药丸 mine #pragma GCC optimize("O3") //By SiriusRen #include <bits/stdc++.h> using namespace std; const int N=100500; typedef long long ll; int n,m,xx,yy,first[N],next[N*2],v[N*2],tot; int in[N],out[N],deep[N],p[N]