zoukankan      html  css  js  c++  java
  • 【BZOJ1095】 Hide 捉迷藏

     

    Time Limit: 4000 ms   Memory Limit: 256 MB

    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

    HINT

      对于100%的数据, N ≤100000, M ≤500000。


    Solution

      题意是有一棵黑白点树,要支持两种操作:将一个点黑白反色,或者查询任意两个黑点的距离的最大值。

      用动态树分治来做:先试着在每一个重心上,维护经过这个重心的两个黑点距离最大值。

      看看能不能维护重心$u$的管辖范围内每个黑点到$u$的距离最大值和次大值,然后加起来就是答案。用一个堆就可以实现,但是发现不好处理两个黑点处在同一子树的问题。

      如果我们能把子树的信息相互独立就好:对于$u$的每一个子树$v$,将$v$子树内所有点到$u$的距离放进一个堆$C_v$里(为了方便,将$C_v$记录在$v$子树的重心$v'$上)。那么对于$u$的每一个$v$子树,将其$C_v$的最大值再组成一个堆$B_u$。因为所有值都互相独立,所以能保证不会出现同一子树情况,因此$B_u$的最大值加上次大值就是经过这个重心的答案。

      对于所有的重心$u$,将$B_u$的最大值加上次大值,扔进一个全局堆$A$中,每次查询输出堆顶即可(如果出现题目描述特殊情况直接输出0或-1)。

      

      进行修改操作时,在点分树上向上迭代。若当前重心为$u$,其父亲为$f$,先用$B_f$的最大值加次大值来删$A$,再用$C_u$的堆顶删$B_f$,更新$C_u$,然后用$C_u$的堆顶更新$B_f$,最后用$B_f$的最大值加次大值更新$A$。  

    可按权值删除的堆:

      这道题中所使用的堆,除了加入一个权值之外,有可能要求从堆中删除一个值为$x$的元素。

      可以自己实现一个可删堆:其中包含一个主堆$a$和删堆$b$。

      增加元素时推入$a$;删除值为$x$的元素时,将$x$推入$b$;进行提取操作(pop();top();)时,如果$a$和$b$堆顶相同则弹出,直到$a$和$b$堆顶不同或$b$为空为止。这样就巧妙地实现了可删堆。

    Tips:

      1. 如果一个重心$u$自己是黑点,那么应该在$B_u$中添加一个值为0的元素(这适用于B堆将两条连拼接起来的本质);否则$B_u$内不应有值为$0$的元素。

      2. 如果一个重心$u$的$B_u$的集合大小小于2,这意味着$u$子树内只用一个黑点。那么不要用$B_u$更新$A$,$A$中不应该存在$B_u$对应的元素,因为此时根本不能组成路径。

      3. 一定要注意优化!改变一个点的状态时,用可用的条件先判断,避免过多的堆操作(真坑爹)。如新加入$C$的值,可与$C$原先堆顶的大小进行判断,如果比原堆顶大,再更新$B$和$A$,否则可以不用进行接下来的操作;删除$C$中的一个值,先判断该值是否和$C$的堆顶相同,如果是才更新$B$和$A$,否则不用操作。


    #include <cstdio>
    #include <queue>
    using namespace std;
    const int N=100005,Bas=19,INF=2147000000;
    int n,q,h[N],tot,cnt;
    int sum,nrt,nval,size[N],cut[N];
    int dep[N],pre[N][Bas];
    int fa[N];
    int st[N];
    struct Edge{int v,next;}g[N*2];
    struct Heap{
        priority_queue<int> a,b;
        int size(){return a.size()-b.size();}
        void push(int x){if(x!=-1) a.push(x);}
        void erase(int x){if(x!=-1) b.push(x);}
        void pop(){
            while(b.size()&&a.top()==b.top()) a.pop(),b.pop();
            a.pop();
        }
        int top(){
            while(b.size()&&a.top()==b.top()) a.pop(),b.pop();
            return a.size()?a.top():-1;
        }
        int sop(){
            if(size()<2) return 0;
            int x=top(); pop();
            int y=top(); push(x);
            return y;
        }
        int count(){
            int sz=size(),x=top(),y=sop();
            if(sz<2) return -1;
            else return x+y;
        }
    }a,b[N],c[N];
    inline int rd(){
        char c;
        int x=0;
        while((c=getchar())<'0'||c>'9');
        x=c-'0';
        while('0'<=(c=getchar())&&c<='9') x=x*10+c-'0';
        return x;
    }
    inline void swap(int &x,int &y){int t=x;x=y;y=t;}
    inline void addEdge(int u,int v){
        g[++tot].v=v; g[tot].next=h[u]; h[u]=tot;
        g[++tot].v=u; g[tot].next=h[v]; h[v]=tot;
    }
    void predfs(int u,int fa,int Dep){
        dep[u]=Dep;
        pre[u][0]=fa;
        for(int i=1;i<Bas;i++) pre[u][i]=pre[pre[u][i-1]][i-1];
        for(int i=h[u],v;i;i=g[i].next)
            if((v=g[i].v)!=fa)
                predfs(v,u,Dep+1);
    }
    int getlca(int a,int b){
        if(dep[a]<dep[b]) swap(a,b);
        for(int i=Bas-1;i>=0;i--)
            if(dep[pre[a][i]]>=dep[b]) a=pre[a][i];
        if(a==b) return a;
        for(int i=Bas-1;i>=0;i--)
            if(pre[a][i]!=pre[b][i]) a=pre[a][i],b=pre[b][i];
        return pre[a][0];
    }
    int getdis(int x,int y){return (dep[x]-1)+(dep[y]-1)-2*(dep[getlca(x,y)]-1);}
    void find(int u,int fa){
        int maxs=0;
        size[u]=1;
        for(int i=h[u],v;i;i=g[i].next)
            if(!cut[v=g[i].v]&&v!=fa){
                find(v,u);
                size[u]+=size[v];
                if(size[v]>maxs) maxs=size[v];
            }
        if(sum-size[u]>maxs) maxs=sum-size[u];
        if(maxs<nval) nrt=u,nval=maxs;
    }
    void dfs(int u,int fa,int dis,Heap &heap){
        heap.push(dis);
        for(int i=h[u],v;i;i=g[i].next)
            if(!cut[v=g[i].v]&&v!=fa)
                dfs(v,u,dis+1,heap);
    }
    void solve(int u,int Fa){
        cut[u]=1; fa[u]=Fa;
        for(int i=h[u],v;i;i=g[i].next)
            if(!cut[v=g[i].v]){
                nval=INF; nrt=0; sum=size[u];        
                find(v,0);
                solve(nrt,u);
            }
    }
    void change(int u,int v,int flag){
        if(u==v){
            if(flag){
                b[u].push(0);
                if(b[u].size()==2) a.push(b[u].top());
            }    
            else{
                if(b[u].size()==2) a.erase(b[u].top());
                b[u].erase(0);
            }
        }
        if(!fa[u]) return;
        int f=fa[u],dis=getdis(f,v),last=c[u].top();
        if(flag) c[u].push(dis); else c[u].erase(dis);
        if((flag&&dis>last)||(!flag&&dis==last)){
            a.erase(b[f].count());
            b[f].erase(last);
            if(flag) b[f].push(dis);
            else b[f].push(c[u].top());
            a.push(b[f].count());
        }
        change(f,v,flag);
    }
    int main(){
        n=rd();
        for(int i=1,u,v;i<n;i++)
            u=rd(),v=rd(),addEdge(u,v);
        predfs(1,0,1);        
        nval=INF; nrt=0; sum=n;
        find(1,0);
        solve(nrt,0);
        for(int i=1;i<=n;i++){
            change(i,i,!st[i]);
            st[i]^=1;
            cnt++;
        }
        char opt[2];
        int x;
        q=rd();
        while(q--){
            scanf("%s",opt);    
            if(opt[0]=='G'){
                if(cnt==1) puts("0");
                else if(cnt==0) puts("-1");
                else printf("%d
    ",a.top());
            }
            else{
                x=rd();
                change(x,x,!st[x]);
                st[x]^=1;
                if(st[x]) cnt++; else cnt--;
            }
        }
        return 0;
    }
    奇妙代码
  • 相关阅读:
    微信公众号开发之用户地理位置坐标转百度坐标
    PHP变量入门教程(1)基础
    【很变态】PHP类实例化对象竟然可以访问类的“静态(static)方法”!!!
    【转】记录PHP、MySQL在高并发场景下产生的一次事故
    PHP返回32位与16位的md5加密值
    PhpStorm 8.x/9.x 快捷键设置/个性化设置,如何多项目共存?如何更换主题?
    Linux设置Memcached开机启动
    【荐】MongoDB基本命令大全
    【荐】PHP操作MongoDB GridFS 存储文件,如图片文件
    Shell入门教程:流程控制(7)break和continue
  • 原文地址:https://www.cnblogs.com/RogerDTZ/p/8045759.html
Copyright © 2011-2022 走看看