[BZOJ2329][HNOI2011]括号修复

直到做完这题我才完全搞清楚Splay的标记下传的正确姿势。顺便说一下,这题是双倍经验题,bzoj2209 [Jsoi2011]括号序列也可以去交,只要把读入输出改一下。

看起来很简单

  1. Replace a b c Solution: Tag!
  2. Swap a b Solution: Tag!
  3. Invert a b Solution: Tag!
  4. Query a b … What the fuck?

Query?

首先还是得膜拜AnOldMan的题解

考虑把一个不合法序列中的合法部分全都消去,那么序列一定会变成类似)))(((((这样的形式。这个时候,答案就是[(右括号数量+1)/2] + [(左括号数量+1)/2]

那么如何统计出消除了合法部分之后剩余的左右括号数量呢?实际上如果把左括号当作+1,右括号当作-1,那么从左边开始,如果出现了一个左括号,相当于阻挡了减小的趋势,我们计算出左起最小值,那么这个就是我们要求的右括号的数量。同理维护右起最大值,就是我们要的左括号数量。

看起来轻松愉悦啊

  1. Replace a b c Solution: Tag!
  2. Swap a b Solution: Tag!
  3. Invert a b Solution: Tag!
  4. Query a b Solution: 维护左起最小和右起最大!

当Replace和Invert同时存在的时候

如果没有考虑到这点的话,那就呵呵呵了。三个tag同时存在的话,清除的顺序不同导致的结果就不同了。首先完全可以无视Swap,因为再怎么换也不会出错,所以先清掉。现在的问题是,同时有ReplaceInvert的时候如何破?

一个很科学的方法是,再额外记录下两个标记出现的时间戳。如果Replace的时间戳早于Invert,那么把Replace的括号反向,并且取消这一次Invert。否则的话,按照先InvertReplace的顺序。

看起来也还好嘛

  1. Replace a b c Solution: Tag!
  2. Swap a b Solution: Tag!
  3. Invert a b Solution: Tag!
  4. Query a b Solution: 维护左起最小和右起最大!
  5. clear顺序Swap > Invert > Replace

嗯?

应该没什么了,只有我这种傻×才会被这种题目卡住8个小时,因为我clear invert的时候忘记取负号了……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include <cstdio>
#include <algorithm>

const int MAXN = 100010;
const int INF = 0x3F3F3f3F;
struct Node
{
  int value, sum, size, lmax, lmin, rmax, rmin, rep, tinv, trep;
  bool inv, swp;
  Node *parent, *child[2];
} _memory[MAXN], *EMPTY = _memory;
int _memory_size = 1;
inline Node* _new_node(const int value, Node* const parent)
{
  Node* const node = _memory+(_memory_size++);
  node->value = node->sum = value;
  node->parent = parent;
  node->child[0] = node->child[1] = EMPTY;
  node->size = 1;
  node->lmax = node->rmax = std::max(0, value);
  node->lmin = node->rmin = std::min(0, value);
  node->rep = node->inv = node->swp = 0;
  node->tinv = node->trep = INF;
  return node;
}
struct SplayTree
{
  Node *Root;
private:
  void _tag_swp(Node* const x)
  {
    if (x == EMPTY) return;
    std::swap(x->child[0], x->child[1]);
    std::swap(x->lmax, x->rmax);
    std::swap(x->lmin, x->rmin);
    x->swp ^= 1;
  }
  void _tag_rep(Node* const x, const int value, const int time)
  {
    if (x == EMPTY) return;
    x->sum = value * x->size;
    x->lmax = x->rmax = std::max(0, x->sum);
    x->lmin = x->rmin = std::min(0, x->sum);
    x->value = x->rep = value, x->trep = time;
  }
  void _tag_inv(Node* const x, const int time)
  {
    if (x == EMPTY) return;
    if (x->rep && x->trep < time)
    {
      _tag_rep(x, -x->rep, time);
      x->inv = false;
      return;
    }
    x->value = -x->value;
    x->sum = -x->sum;
    std::swap(x->lmin, x->lmax);
    std::swap(x->rmin, x->rmax);
    x->lmin = -x->lmin, x->lmax = -x->lmax;
    x->rmin = -x->rmin, x->rmax = -x->rmax;
    x->inv ^= 1, x->tinv = time;
  }
  void clear(Node* const x)
  {
    if (x == EMPTY) return;
    if (x->swp)
    {
      _tag_swp(x->child[0]);
      _tag_swp(x->child[1]);
      x->swp = false;
    }
    if (x->inv)
    {
      _tag_inv(x->child[0], x->tinv);
      _tag_inv(x->child[1], x->tinv);
      x->inv = false, x->tinv = INF;
    }
    if (x->rep)
    {
      _tag_rep(x->child[0], x->rep, x->trep);
      _tag_rep(x->child[1], x->rep, x->trep);
      x->rep = 0, x->trep = INF;
    }
  }
  void update(Node* const x)
  {
    x->sum = x->child[0]->sum + x->value + x->child[1]->sum;
    x->size = x->child[0]->size + 1 + x->child[1]->size;
    x->lmax = std::max(x->child[0]->lmax, x->child[0]->sum + x->value + x->child[1]->lmax);
    x->lmin = std::min(x->child[0]->lmin, x->child[0]->sum + x->value + x->child[1]->lmin);
    x->rmax = std::max(x->child[1]->rmax, x->child[1]->sum + x->value + x->child[0]->rmax);
    x->rmin = std::min(x->child[1]->rmin, x->child[1]->sum + x->value + x->child[0]->rmin);
  }
  void rotate(Node* const x, const int c)
  {
    Node* const y = x->parent;
    y->child[c^1] = x->child[c];
    if (x->child[c] != EMPTY)
      y->child[c^1]->parent = y;
    x->parent = y->parent;
    if (y->parent != EMPTY)
      x->parent->child[y == y->parent->child[1]] = x;
    x->child[c] = y;
    y->parent = x;
    update(y);
  }
  void splay(Node* const x, Node* const goal = EMPTY)
  {
    clear(x);
    for (Node *y, *z; (y = x->parent) != goal; )
    {
      clear(z = y->parent), clear(y), clear(x);
      if (z == goal)
        { rotate(x, x == y->child[0]); break; }
      const int c = y == z->child[0];
      if (x == y->child[c]) rotate(x, c^1), rotate(x, c);
      else rotate(y, c), rotate(x, c);
    }
    if (goal == EMPTY)
      Root = x;
    update(x);
  }
  Node* getkth(int k, Node* const to = EMPTY)
  {
    Node* x = Root;
    ++k;
    for (clear(x); x->child[0]->size+1 != k; clear(x))
      if (x->child[0]->size >= k)
        x = x->child[0];
      else
        { k -= x->child[0]->size+1; x = x->child[1]; }
    splay(x, to);
    return x;
  }
public:
  SplayTree(): Root(EMPTY) { }
  void Build(const char* s)
  {
    Root = _new_node(0, EMPTY);
    Node* x = Root;
    for (; *s; x = x->child[1])
      x->child[1] = _new_node(*s++ == '(' ? +1 : -1, x);
    x->child[1] = _new_node(0, x);
    for (Node* y = x->child[1]; y != EMPTY; y = y->parent) update(y);
    splay(x);
  }
  void SeqRep(const int lef, const int rig, const int value, const int time)
  {
    Node* const ed = getkth(rig+1, getkth(lef-1));
    _tag_rep(ed->child[0], value, time);
    for (Node* x = ed; x != EMPTY; x = x->parent) update(x);
  }
  void SeqInv(const int lef, const int rig, const int time)
  {
    Node* const ed = getkth(rig+1, getkth(lef-1));
    _tag_inv(ed->child[0], time);
    for (Node* x = ed; x != EMPTY; x = x->parent) update(x);
  }
  void SeqSwp(const int lef, const int rig)
  {
    Node* const ed = getkth(rig+1, getkth(lef-1));
    _tag_swp(ed->child[0]);
    for (Node* x = ed; x != EMPTY; x = x->parent) update(x);
  }
  int SeqQuery(const int lef, const int rig)
  {
    Node* const ed = getkth(rig+1, getkth(lef-1));
    return (-ed->child[0]->lmin+1)/2 + (ed->child[0]->rmax+1)/2;
  }
};

int n, m;
char s[MAXN];
SplayTree seq;
int main()
{
  EMPTY->child[0] = EMPTY->child[1] = EMPTY->parent = EMPTY;
  scanf("%d%d%s", &n, &m, s);
  seq.Build(s);
  for (int i = 0, x, y; i < m; ++i)
  {
    scanf("%s%d%d", s, &x, &y);
    switch (s[0])
    {
      case 'R': scanf("%s", s); seq.SeqRep(x, y, s[0] == '(' ? +1 : -1, i); break;
      case 'S': seq.SeqSwp(x, y); break;
      case 'I': seq.SeqInv(x, y, i); break;
      case 'Q': printf("%d\n", seq.SeqQuery(x, y)); break;
    }
  }
}

一点题外话

我发现一个比较科学的请标记的方法是:对于每一个标记写一个专门打标记的方法,先把维护好这个节点的信息,然后这个节点记录的tag代表要传给孩子的tag。每次做clear的时候,按照顺序检查标记,然后给两个孩子打标记。

Splay清除标记的时候,在splay过程里面,从x的爷爷开始祖孙三代依次clear,其他时候都没有必要clear。

似乎只要同时存在InvertReplace的题目,就得用这种奇怪的时间戳的方法(不管是平衡树还是线段树)。我也想过不用时间戳,就是保证每个节点除了Swap以外,只有一个标记。没有试过对不对,但是从直觉上来讲,有点行不通:对于某个子节点,你希望它只有一个tag,但是它的父亲可能在不知不觉中就分两次传给了它不同的tag,而且要命的是,你根本不知道哪一个tag是真正先打下去的(跟传下去不一样)。

如果做完这题,再去做bzoj1858 [Scoi2010]序列操作会发现很愉悦(线段树就好了,别什么都打平衡树T_T)。

Comments