【bzoj1905】捉迷藏(线段树)

Description

    捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋
子都互相可达。游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。 我们将以如下形式定义每一种操作: C(hange) i 改变第i个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。 G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。

Input

     第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。接下来N-1行每行两个整数a, b,表示房间a与房间b之间有一条走廊相连。接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如上文所示。

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
G

Sample Output

4
3
3
4

I think

    膜岛娘题解
    一棵树上两点之间的距离等于该树括号序列中两点间非匹配括号数量之和。将括号序列与房间标号存在一个序列中,用线段树维护五个变量,每个区间用用其所含不匹配的左(b)右(a)括号(a,b)数对表示,l1,l2,r1,r2,dis,分别代表该区间内左端为该区间左端右端为黑点且a’+b’最大的子序列,左端为该区间左端右端为黑点且b’-a’最大的子序列(注意是b’-a’而不是a’-b’),右端为该区间右端且左端为黑点的a’+b’最大的子序列,右端为该区间右端且左端为黑点的a’-b’最大的子序列,区间内最远两点之间的最大距离。
    那么维护dis有:

dis[i]=Max(dis[i<<1],  dis[i<<1|1],  r1i<<1+l2i<<1|1,  r2i<<1+l1i<<1|1

    其他变量的维护有些复杂可看岛娘题解自己手推一下……

Code

#include
#include
#include
#define M 300000+50
#define N 1200000+50
#define inf 1e9
using namespace std;
int n,q,x,y,s,t,sum;
int c[M],h[M],to[M],nxt[M],pos[M],lgh[M];
// lgh[i] 房间i关灯 c[]包含括号与房间灯的序列 pos[i] 房间i在c[]中对应的编号
struct node {
    int lp,rp,lm,rm,a,b,dis;//lp - leftplus rm - rightminus
    void inicial(int x) {
        dis=-inf;a=b=0;
        if(c[x]==-1) b=1; //b代表区间左括号数
        if(c[x]==-2) a=1;//a代表区间右括号数
        if(c[x]>0&&lgh[c[x]]) lp=rp=lm=rm=0; //灯是关着的
        else lp=rp=lm=rm=-inf;
    }
}st[N];
int Max(int x,int y) { return x>y?x:y; } 
char ch;
void read(int &x) {
    x=0,ch=getchar();
    while(ch>'9'||ch<'0')ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void add(int x,int y) {
    to[++s]=y,nxt[s]=h[x],h[x]=s;
    to[++s]=x,nxt[s]=h[y],h[y]=s;
}
void dfs(int x,int fa) {
    c[++t]=-1;
    c[pos[x]=++t]=x;//最开始所有的灯都是关着的
    for(int i=h[x];i;i=nxt[i]) {
        if(to[i]==fa)continue;
        dfs(to[i],x);
    }
    c[++t]=-2;
}
void update(int k) {
    int ls=k<<1,rs=k<<1|1;
    int a=st[ls].a,b=st[ls].b,c=st[rs].a,d=st[rs].b;

    st[k].dis=Max(st[ls].dis,st[rs].dis);//更新区间最大值
    st[k].dis=Max(st[k].dis,Max(st[ls].rp+st[rs].lm,st[ls].rm+st[rs].lp));
    //跨中间的两段合并 保证两端都有黑点

    if(b>=c) st[k].a=a,st[k].b=b-c+d;//更新当前区间左右括号数
    else st[k].a=a+c-b,st[k].b=d;

    st[k].rp=Max(st[rs].rp,Max(st[ls].rp-c+d,st[ls].rm+c+d));//更新lp,rp,lm,rm
    st[k].lp=Max(st[ls].lp,Max(st[rs].lp+a-b,st[rs].lm+a+b));
    st[k].rm=Max(st[rs].rm,st[ls].rm+c-d);
    st[k].lm=Max(st[ls].lm,st[rs].lm+b-a);
    //leftminus维护的是max{b-a|S’(a,b)是S的一个前缀,且有一个黑点紧接在S之后}
}
void build(int k,int l,int r) {
    if(l==r) { st[k].inicial(l); return; }
    int m=(l+r)>>1;
    build(k<<1,l,m);
    build(k<<1|1,m+1,r);
    update(k);
}
void modify(int k,int l,int r,int pos) {
    if(l==r) { st[k].inicial(l); return; }
    int m=(l+r)>>1;
    if(pos<=m) modify(k<<1,l,m,pos);
    else modify(k<<1|1,m+1,r,pos);
    update(k);
}
int main() {
    read(n),sum=n;
    for(int i=1;i<=n;++i) lgh[i]=1;
    for(int i=1;i1,0);
    build(1,1,t);
    read(q);
    while(q--) {
        ch=getchar();
        if(ch=='C') {
            read(x);
            if(!lgh[x]) ++sum;//sum记关灯数
            else --sum;
            lgh[x]^=1;
            modify(1,1,t,pos[x]);
        }
        else {
            if(sum==1)puts("0");
            else if(sum==0)puts("-1");
            else printf("%d\n",st[1].dis);
            ch=getchar();
        }
    }
    return 0;
}

你可能感兴趣的:(线段树)