• 「BZOJ1095」[ZJOI2007] Hide 捉迷藏


    题目描述

    Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达。

    游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。

    我们将以如下形式定义每一种操作:

    • C(hange) i 改变第i个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
    • G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。

    输入输出格式

    输入格式:

    第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。

    接下来N-1行每行两个整数a, b,表示房间a与房间b之间有一条走廊相连。

    接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如上文所示。

    输出格式:

    对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关着灯的,输出0;若所有房间的灯都开着,输出-1。

    输入输出样例

    输入样例#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
    ",a[1].dis);
    109         }
    110     }
    111     return 0;
    112 }
  • 相关阅读:
    SQL逻辑查询处理阶段
    将json字符串转换为json兑现
    JSTL核心标签库
    eclipse用4个空格代替Tab 每行80字符限制提示线显示空格
    MyEclipse8.6 性能优化
    jsp/servlet总结复习
    SQLServer之MERGE INTO
    as3效率提升
    让默认TextField更清晰地显示中文
    as3垃圾回收机制
  • 原文地址:https://www.cnblogs.com/bztMinamoto/p/9381655.html
Copyright © 2020-2023  润新知