BZOJ1095: [ZJOI2007]Hide 捉迷藏【線段樹維護括號序列】【思惟好題】

Description

  捉迷藏 Jiajia和Wind是一對恩愛的夫妻,而且他們有不少孩子。某天,Jiajia、Wind和孩子們決定在家裏玩
捉迷藏遊戲。他們的家很大且構造很奇特,由N個屋子和N-1條雙向走廊組成,這N-1條走廊的分佈使得任意兩個屋
子都互相可達。遊戲是這樣進行的,孩子們負責躲藏,Jiajia負責找,而Wind負責操縱這N個屋子的燈。在起初的
時候,全部的燈都沒有被打開。每一次,孩子們只會躲藏在沒有開燈的房間中,可是爲了增長刺激性,孩子們會要
求打開某個房間的電燈或者關閉某個房間的電燈。爲了評估某一次遊戲的複雜性,Jiajia但願知道可能的最遠的兩
個孩子的距離(即最遠的兩個關燈房間的距離)。 咱們將以以下形式定義每一種操做: C(hange) i 改變第i個房
間的照明狀態,若原來打開,則關閉;若原來關閉,則打開。 G(ame) 開始一次遊戲,查詢最遠的兩個關燈房間的
距離。c++

Input

  第一行包含一個整數N,表示房間的個數,房間將被編號爲1,2,3…N的整數。接下來N-1行每行兩個整數a, b,
表示房間a與房間b之間有一條走廊相連。接下來一行包含一個整數Q,表示操做次數。接着Q行,每行一個操做,如
上文所示。ide

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
Gui

Sample Output

4
3
3
4spa

HINT

對於100%的數據, N ≤100000, M ≤500000。code


思路

首先來了解一下括號序列怎麼生成遊戲

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] = ')';
}

這樣就獲得了一個括號序列ip

好比樣例中的序列就是\((1(2(3(6(8)(7))(5)(4))))\)input

而後若是把數字去掉,就發現這個序列變成了\(((((()())()())))\)it

其實能夠簡單理解爲\((\)是向下,\()\)是向上那麼咱們如何利用這個信息

若是咱們要計算兩點的距離,先把這兩點之間的括號序列提取出來

好比說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;
}
相關文章
相關標籤/搜索