zoukankan      html  css  js  c++  java
  • bzoj1095 [ZJOI2007]Hide 捉迷藏

    1095: [ZJOI2007]Hide 捉迷藏

    Time Limit: 40 Sec  Memory Limit: 256 MB
    Submit: 5009  Solved: 2072
    [Submit][Status][Discuss]

    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。

    分析:非常难的一道题.

       首先这道题看上去很像数据结构题:多种操作,修改询问什么的都有.  

       将关着的灯看作黑点,开着的灯看作白点.

       如果只有询问操作,是可以dp的. 令f[i][0/1]表示以i为根的子树中的黑点到i的最长距离/次长距离. 类似维护最长链一样,先从下往上求一次,再从上往下求一次. 但是这道题有修改操作,每次修改完以后就必须再做一次dp.复杂度太高.dp这个方法行不通. 这种看上去很像数据结构题的题一般dp是行不通的。

       还是考虑只有询问操作.路径问题有没有什么好的方法呢? 树剖?显然不行吧...... 点分治?emm,似乎可行. 用点分治的话就是类似于bzoj3697的方法了,将树“看作”二叉树,维护前缀信息来做.但你听说过带修改操作的点分治吗?

       其实是有这种点分治方法的.一般称之为动态点分治,它维护了一个点分树,修改查询只会在点分树的节点上进行. 点分树的深度是logn的,所以修改查询的复杂度是O(logn). 那么什么是点分树呢?

       一般的点分治的题目的步骤是:找重心-在重心节点上dfs-找重心-继续dfs.如果某一次dfs的点是i,下一次dfs的点是j,则i,j连一条边.简而言之,就是把点分治中每个重心的父亲设为它上一层的重心,并在重心处记录下它“管辖”的子树的信息.

       点分树特别像线段树,如果说线段树每一个点维护了一段区间的信息,那么点分树中的每一个节点就维护了若干个子树的信息.这对于理解点分树特别有用.

       建出点分树很容易,点分树上的每个节点维护什么信息呢?我一开始想的是对每一个节点维护一个大根堆,记录这个节点管辖的点到这个点的距离.每次取出最大和次大的组成一个二元组,再插入到一个全局堆里面. 询问的话直接输出这个全局堆的堆顶就好了. 看起来似乎没问题.但有一种情况不行:

    黑色点是重心,红色点分别是最长和次长的,查询得到的答案会是3+4 = 7. 事实上它们之间的距离是1. 

       维护的信息不足以保证正确性.那就多维护点信息呗.对于每个节点维护两个堆.A堆维护这个重心管辖的所有点到父亲重心的距离.

    B堆维护它的所有下一层重心的A堆的堆顶. 再开一个全局堆C,维护每一个B堆的最大值+次大值. 比较像线段树维护信息的方式.每次从子节点提取信息.

       查询还是一样的,如果点数≥2输出C堆堆顶就好了. 但是修改就非常恶心了,每一次修改会影响当前修改的点  所在层的重心  到点分树的根  的路径上的  所有点.

       如果是白点变成黑点.相当于插入了一个黑点嘛.对每一层的重心进行修改. 如果当前的重心的A堆是空的,也就是点全是白点了,那么直接插入A堆即可,并且更改当前重心的父亲的B堆.否则看插入的点是不是A堆中最大的.如果是的话,就要把之前那个A堆中最大的从B堆中删除,维护C堆,然后插入当前值. 否则直接插入就好了.

       如果是从黑点变成白点,相当于删除一个点,那么看删除的这个点是不是A堆中权值最大的,如果是,那么就要从B堆中删除它,并维护C堆,否则直接删就好了.

       维护B,C堆信息也要讨论.

       如果向B堆中插入一个点. B堆中点的数量小于2的话,直接插入B,如果插入了以后点的数量等于2,则将这两个点组成一个二元组插入到C. 如果一开始数量大于2,C堆删掉B堆的最大值+次大值,B堆插入这个值,C堆插入B堆的最大值+次大值.

       如果从B堆中删除一个点. 如果B堆中点的数量小于等于2,直接删,如果删了后数量等于1,则从C堆中删掉B堆中剩下的数和已经删掉的这个数(组成不了二元组了).如果数量大于2,那么先从C堆中删掉B堆的最大值+次大值,然后从B堆中删掉这个数,再向C堆中加入B堆的最大值+次大值.

       考虑的过程就是:修改A堆 --> 对B堆造成影响 --> 对C堆造成影响.  另外要注意:C堆中维护的每一个B堆的点数都要≥2.

       还有一个问题:优先队列要怎么删数啊? 一个非常巧妙的做法:再维护一个大根堆! 如果要删掉x,就将x丢进删除堆中.每次pop或者top操作时,将原堆和删除堆中相同数弹掉(从堆顶开始弹,直到两个堆顶元素不同或者删除堆为空).

       剩下的就是一些细节问题了,总之,这是一道非常变态的题!

    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int maxn = 200010;
    int head[maxn],to[maxn],nextt[maxn],col[maxn],tot = 1,root,fa2[maxn];
    int n,Q,fa[maxn][20],dep[maxn],f[maxn],sum,num,sizee[maxn],vis[maxn];
    
    struct node
    {
        priority_queue <int> p,q;
        void push(int x)
        {
            p.push(x);
        }
        void erase(int x)
        {
            q.push(x);
        }
        void update()  
        {
            while (q.size() && q.top() == p.top())
            {
                p.pop();
                q.pop();
            }
        }
        int top()
        {
            update();
            return p.top();
        }
        int sec()  //求第二大
        {
            update();
            int x = p.top();
            update();
            p.pop();
            update();
            int y = p.top();
            update();
            p.push(x);
            return y;
        }
        int size()  //容易写错
        {
            return p.size() - q.size();
        }
    } A[maxn],B[maxn],C;
    
    void add(int x,int y)
    {
        to[tot] = y;
        nextt[tot] = head[x];
        head[x] = tot++;
    }
    
    void dfs(int u,int faa)
    {
        fa[u][0] = faa;
        dep[u] = dep[faa] + 1;
        for (int i = head[u]; i; i = nextt[i])
        {
            int v = to[i];
            if (v == faa)
                continue;
            dfs(v,u);
        }
    }
    
    void findroot(int u,int faa)
    {
        sizee[u] = 1;
        f[u] = 0;
        for (int i = head[u]; i; i = nextt[i])
        {
            int v = to[i];
            if (v == faa || vis[v])
                continue;
            findroot(v,u);
            sizee[u] += sizee[v];
            f[u] = max(f[u],sizee[v]);
        }
        f[u] = max(f[u],sum - sizee[u]);
        if (f[u] < f[root])
            root = u;
    }
    
    int lca(int x,int y)
    {
        if (dep[x] < dep[y])
            swap(x,y);
        for (int i = 19; i >= 0; i--)
            if (dep[fa[x][i]] >= dep[y])
                x = fa[x][i];
        if (x == y)
            return x;
        for (int i = 19; i >= 0; i--)
            if (fa[x][i] != fa[y][i])
            {
                x = fa[x][i];
                y = fa[y][i];
            }
        return fa[x][0];
    }
    
    int dist(int x,int y)
    {
        return dep[x] + dep[y] - 2 * dep[lca(x,y)];
    }
    
    void build(int u)
    {
        vis[u] = 1;
        for (int i = head[u]; i; i = nextt[i])
        {
            int v = to[i];
            if (vis[v])
                continue;
            f[0] = sum = sizee[v];
            findroot(v,root = 0);
            fa2[root] = u;
            build(root);
        }
    }
    
    void init()
    {
        for (int i = 1; i <= n; i++)
        {
            int now = i;
            while (fa2[now])  
            {
                A[now].push(dist(fa2[now],i));
                now = fa2[now];
            }
        }
        for (int i = 1; i <= n; i++)
            if (fa2[i])
                B[fa2[i]].push(A[i].top());
        for (int i = 1; i <= n; i++)
            if (B[i].size() >= 2)
                C.push(B[i].top() + B[i].sec());
    }
    
    void update1(int x)
    {
        int faa = fa2[x];
        if (B[faa].size() < 2)
        {
            B[faa].push(A[x].top());
            if (B[faa].size() == 2)
                C.push(B[faa].top() + B[faa].sec());
        }
        else
        {
            int temp = B[faa].top() + B[faa].sec();
            C.erase(temp);
            B[faa].push(A[x].top());
            C.push(B[faa].top() + B[faa].sec());
        }
    }
    
    void update2(int x,int y)
    {
        int faa = fa2[x];
        if (B[faa].size() <= 2)
        {
            B[faa].erase(y);
            if (B[faa].size() == 1)
                C.erase(B[faa].top() + y);
        }
        else
        {
            C.erase(B[faa].top() + B[faa].sec());
            B[faa].erase(y);
            C.push(B[faa].top() + B[faa].sec());
        }
    }
    
    void update(int x)
    {
        if (col[x])
        {
            int now = x;
            while (fa2[now])
            {
                if (A[now].size() == 0)
                {
                    A[now].push(dist(x,fa2[now]));
                    update1(now);
                }
                else
                {
                    int temp = A[now].top();
                    A[now].push(dist(x,fa2[now]));
                    if (A[now].top() != temp)
                    {
                        update2(now,temp);
                        update1(now);
                    }
                }
                now = fa2[now];
            }
        }
        else
        {
            int now = x;
            while (fa2[now])
            {
                int temp = dist(x,fa2[now]);
                if (temp == A[now].top())
                {
                    A[now].erase(temp);
                    update2(now,temp);
                    if (A[now].size())
                        update1(now);
                }
                else
                    A[now].erase(temp);
                now = fa2[now];
            }
        }
        col[x] ^= 1;
    }
    
    int main()
    {
        scanf("%d",&n);
        for (int i = 1; i < n; i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);
            add(y,x);
        }
        dfs(1,0);  //为了求LCA和两点距离
        for (int j = 1; j <= 19; j++)
            for (int i = 1; i <= n; i++)
                fa[i][j] = fa[fa[i][j - 1]][j - 1];
        f[0] = sum = num = n;
        findroot(1,0);  //找根
        build(root);  //建点分树
        init();  //初始化所有黑点的信息
        scanf("%d",&Q);
        while (Q--)
        {
            char ch[3];
            scanf("%s",ch);
            if (ch[0] == 'G')
            {
                if (num < 2)
                    printf("%d
    ",num - 1);
                else
                    printf("%d
    ",C.top());
            }
            else
            {
                int x;
                scanf("%d",&x);
                update(x);
                if (col[x])
                    num--;
                else
                    num++;
            }
        }
    
        return 0;
    }

       

       

  • 相关阅读:
    Deep Learning Enables You to Hide Screen when Your Boss is Approaching
    Creating your own auto-configuration
    为什么手机连接wifi会显示已停用?
    关于新版SDK报错You need to use a Theme.AppCompat theme的两种解决办法
    Android问题集锦之二十八:You need to use a Theme.AppCompat theme (or descendant) with this activity.
    Material Design系列,自定义Behavior实现Android知乎首页
    Android自动填充短信验证码
    Android开发将List转化为JsonArray和JsonObject
    Android List<Map<String,String>转json(例子)
    Android中关于List与Json转化问题
  • 原文地址:https://www.cnblogs.com/zbtrs/p/8519979.html
Copyright © 2011-2022 走看看