「BZOJ1095」[ZJOI2007] Hide 捉迷藏

題目描述html

Jiajia和Wind是一對恩愛的夫妻,而且他們有不少孩子。某天,Jiajia、Wind和孩子們決定在家裏玩捉迷藏遊戲。他們的家很大且構造很奇特,由N個屋子和N-1條雙向走廊組成,這N-1條走廊的分佈使得任意兩個屋子都互相可達。c++

遊戲是這樣進行的,孩子們負責躲藏,Jiajia負責找,而Wind負責操縱這N個屋子的燈。在起初的時候,全部的燈都沒有被打開。每一次,孩子們只會躲藏在沒有開燈的房間中,可是爲了增長刺激性,孩子們會要求打開某個房間的電燈或者關閉某個房間的電燈。爲了評估某一次遊戲的複雜性,Jiajia但願知道可能的最遠的兩個孩子的距離(即最遠的兩個關燈房間的距離)。git

咱們將以以下形式定義每一種操做:ide

  • C(hange) i 改變第i個房間的照明狀態,若原來打開,則關閉;若原來關閉,則打開。
  • G(ame) 開始一次遊戲,查詢最遠的兩個關燈房間的距離。

輸入輸出格式

輸入格式:優化

第一行包含一個整數N,表示房間的個數,房間將被編號爲1,2,3…N的整數。ui

接下來N-1行每行兩個整數a, b,表示房間a與房間b之間有一條走廊相連。編碼

接下來一行包含一個整數Q,表示操做次數。接着Q行,每行一個操做,如上文所示。spa

輸出格式:code

對於每個操做Game,輸出一個非負整數到hide.out,表示最遠的兩個關燈房間的距離。若只有一個房間是關着燈的,輸出0;若全部房間的燈都開着,輸出-1。htm

輸入輸出樣例

輸入樣例#1:
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
輸出樣例#1:
4
3
3
4

說明

對於20%的數據, N ≤50, M ≤100;

對於60%的數據, N ≤3000, M ≤10000; 對於100%的數據, N ≤100000, M ≤500000。

題解

神仙般的操做……

膜拜島娘的思路和hzwer的代碼……

咱們先假設有以上這麼一棵樹(圖醜勿介)

進行先序遍歷,獲得$[A[B[E][F[H][I]]][C][D[G]]]$

再把全部字母去掉$[ [ [ ] [ [ ] [ ] ] ] [ ] [ [ ] ] ]$

這就是這一棵樹的括號編碼(本質是dfs獲得的)

花了這麼大功夫找,但這玩意兒到底有什麼用呢?

咱們考慮兩個節點,E和G

取出他們之間的那段括號編碼$] [ [ ] [ ] ] ] [ ] [ [$

再將全部匹配的括號去掉,獲得$] ] [ [$

咱們看到了兩個$]$和兩個$[$

再回到樹上,咱們發現E向上走兩步,再向下走兩步就到達了G

因而發現括號序列能夠很方便地維護點與點之間的距離

能不能進一步優化呢?

咱們發現,對於距離而言,匹配的括號是沒有任何意義的

並且,因爲距離只須要記錄數字,因此維護括號也是沒有意義的,只要有編碼就行,能夠用一個二元組$(a,b)$來描述它,表示有a個$]$和b個$[$

因此,若是有兩個點P和Q,若是介於P和Q之間的括號編碼表示爲$(a,b)$,則P和Q在樹上的距離就是a+b

是否是很方便啊~\(≧▽≦)/~啦啦啦

可是如今問題又來了,怎麼維護編碼呢?

若是能夠經過左邊一半的信息和右邊一半的信息,從而獲得整段編碼的信息,就能夠用咱們熟悉的線段樹來維護了

咱們能夠進行以下的分析

考慮對於兩段括號編碼$s1(a,b)$和$s2(c,d)$,他們合併起來能夠獲得$s(x,y)$

注意到$s1$和$s2$合併起來時會產生$min(b,c)$的匹配括號,合併後他們會被抵消掉

因而

當 $b<c$ 時第一段 [ 就被消完了,兩段 $]$ 連在一塊兒,例如:

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

當 $b>=c$ 時第二段 ] 就被消完了,兩段 $[ $連在一塊兒,例如:
$]  ]  [  [  [  +  ]  ]  [  [  = ] ]  [  [  [$

因而就獲得了幾個十分有用的結論

當 $b<c$ 時,$(x,y) = (a-b+c,d)$

當 $b>=c$ 時,$(x,y) = (a,b-c+d)$

因而就能夠用線段樹維護整棵樹的括號編碼~\(≧▽≦)/~啦啦啦

題目所要求維護的,是$max\{a+b|s'(a,b)是s的一個子串,且s'位於兩黑點之間\}$,咱們將這個值表示爲$dis(s)$

咱們先根據上面的兩條結論,獲得幾個推論

$①x+y=a+d+|b-c|=max((a+b-c+d),(a-b+c+d))$

$②x-y=a-b+c-d$

$③y-x=b-a+d-c$

由①式咱們能夠發現,要維護$dis(s)$,要維護四個值$a+b,d-c,a-b,d+c$

又爲了保證$s'$在兩個黑點之間,因此要加上一些限制

因而定義出以下四個參數

$rightplus:max(a+b),s'是s的一個前綴且s緊接在一個黑點以後$
$rightminus:max(a-b),s'是s的一個前綴且s緊接在一個黑點以後$
$leftplus:max(a+b),s'是s的一個後綴且一個黑點緊接在s以後$
$leftminus:max(b-a),s'是s的一個後綴且一個黑點緊接在s以後$

因而咱們就能夠用左右兩半的狀態轉移到一整段的狀態啦

仍是考慮$s(x,y),s1(a,b),s2(c,d)$

$(x,y)=b<c?(a-b+c,d):(a,b-c+d)$

$dis(s)=max(dis(s1),dis(s2),rightplus(s1)+leftminus(s2),rightminus(s1)+leftplus(s2))$

(把四個參數的值帶入上面的等式很容易發現這是正確的)

而後再來考慮如何求出四個參數呢?

$rightplus(s)=max(rightplus(s1)-c+d,rightminus(s1)+c+d,rightplus(s2))$

$rightminus(s)=max(rightminus(s1)+c-d,rightminus(s2))$

$leftplus(s)=max(leftplus(s2)-b+a,left_minus(s1)+b+a,leftplus(s1))$

$leftminus(s)=max(leftminus(s2)+b-a,leftminus(s1))$

而後就能夠用線段樹處理整個括號編碼了

實際實現的時候還有一些小細節要注意

咱們爲了實現更方便,最好仍是在編碼時加入括號

對於底層結點,若是對應字符是一個括號或者一個白點,那 麼right_plus、right_minus、left_plus、left_minus、dis 的值就都是 -inf;若是對應字符是一個黑點,那麼 right_plus、right_minus、left_plus、left_minus 都是 0,dis 是-inf。

具體細節能夠參見代碼,註解比較詳細(主要是由於本身照着打了一遍也不太看得懂代碼……)

  1 //minamoto
  2 #include<bits/stdc++.h>
  3 #define N 100005
  4 #define inf 0x3f3f3f3f
  5 using namespace std;
  6 inline int read(){
  7     #define num ch-'0'
  8     char ch;bool flag=0;int res;
  9     while(!isdigit(ch=getchar()))
 10     (ch=='-')&&(flag=true);
 11     for(res=num;isdigit(ch=getchar());res=res*10+num);
 12     (flag)&&(res=-res);
 13     #undef num
 14     return res;
 15 }
 16 int ver[N<<1],Next[N<<1],head[N];
 17 int v[N*3],pos[N],c[N];
 18 int n,q,cnt,tot,black;
 19 struct seg{
 20     int l,r,l1,l2,r1,r2,c1,c2,dis;
 21     void init(int x){
 22         dis=-inf;
 23         c1=c2=0;
 24         if(v[x]==-1) c2=1;
 25         if(v[x]==-2) c1=1;
 26         /*c2爲失配左括號,c1爲失配右括號 
 27         爲左括號,c2=1;爲右括號,c1=1*/
 28         if(v[x]>0&&c[v[x]]) l1=l2=r1=r2=0;
 29         else l1=l2=r1=r2=-inf;
 30         /*爲黑點,l_plus,l_minus,r_plus,r_minus全爲0 
 31         爲白點或括號,全爲1*/
 32     }
 33 }a[N*12];
 34 inline int max(int a,int b,int c){return max(a,max(b,c));}
 35 void add(int u,int v){
 36     ver[++tot]=v,Next[tot]=head[u],head[u]=tot;
 37     ver[++tot]=u,Next[tot]=head[v],head[v]=tot;
 38 }
 39 void dfs(int u,int fa){
 40     v[++cnt]=-1;
 41     v[++cnt]=u;
 42     pos[u]=cnt;
 43     for(int i=head[u];i;i=Next[i])
 44     if(ver[i]!=fa) dfs(ver[i],u);
 45     v[++cnt]=-2;
 46     /*進入加左括號,離開加右括號*/
 47 }
 48 inline void merge(seg &s,seg s1,seg s2){
 49     /*r1=max(a+b),r2=max(a-b){s1(a,b)是s前綴且s1緊接在一個黑點以後}
 50     l1=max(a+b),l2=max(b-a){s2(a,b)是s後綴且s2緊接在一個黑點以前}*/
 51     int a=s1.c1,b=s1.c2,c=s2.c1,d=s2.c2;
 52     s.dis=max(s1.dis,s2.dis);
 53     s.dis=max(s.dis,s1.r1+s2.l2,s1.r2+s2.l1);
 54     /*s.dis=max(s1.dis,s2.dis,a1+b1-a2+b2,a1-b1+a2+b2)*/ 
 55     b<c?(s.c1=a-b+c,s.c2=d):(s.c1=a,s.c2=b-c+d);
 56     s.r1=max(s2.r1,s1.r1-c+d,s1.r2+c+d);
 57     /*a+b=max(a1-b1+a2+b2,a1+b1+b2-a2)*/
 58     s.r2=max(s2.r2,s1.r2+c-d);
 59     /*a-b=a1-b1+a2-b2*/
 60     s.l1=max(s1.l1,s2.l1-b+a,s2.l2+b+a);
 61     /*同上*/
 62     s.l2=max(s1.l2,s2.l2+b-a);
 63     /*b-a=b2-a2+b1-a1*/
 64 }
 65 void build(int p,int l,int r){
 66     a[p].l=l,a[p].r=r;
 67     if(l==r){
 68         a[p].init(l);
 69         return;
 70     }
 71     int mid=(l+r)>>1;
 72     build(p<<1,l,mid);
 73     build(p<<1|1,mid+1,r);
 74     merge(a[p],a[p<<1],a[p<<1|1]);
 75 }
 76 void modify(int p,int x){
 77     int l=a[p].l,r=a[p].r;
 78     if(l==r){a[p].init(l);return;}
 79     int mid=(l+r)>>1;
 80     if(x<=mid) modify(p<<1,x);
 81     else modify(p<<1|1,x);
 82     merge(a[p],a[p<<1],a[p<<1|1]);
 83 }
 84 int main(){
 85     //freopen("testdata.in","r",stdin);
 86     black=n=read();
 87     for(int i=1;i<=n;++i) c[i]=1;
 88     for(int i=1;i<n;++i){
 89         int u=read(),v=read();
 90         add(u,v);
 91     }
 92     dfs(1,0);
 93     build(1,1,cnt);
 94     q=read();
 95     while(q--){
 96         char s[10];
 97         scanf("%s",s);
 98         if(s[0]=='C'){
 99             int x=read();
100             if(c[x]) --black;
101             else ++black;
102             c[x]^=1;
103             modify(1,pos[x]);
104         }
105         else{
106             if(!black) puts("-1");
107             else if(black==1) puts("0");
108             else printf("%d\n",a[1].dis);
109         }
110     }
111     return 0;
112 }
相關文章
相關標籤/搜索