zoukankan      html  css  js  c++  java
  • 线段树 区间合并

    线段树 区间合并 - HDU - 1540 - Tunnel Warfare

    个人解法

    思路介绍

    1. 建树

    所需要的全局变量

    int stk_ptr = 0; // 栈顶指针
    int stk[N]; // 模拟栈
    char cmd[5]; // 接受命令'D','R','Q'
    int find_leaf[N]; // 根据村庄编号访问其代表的叶子结点的序号
    pii get_lr[N<<2]; // 获得当前结点所代表的村庄区间[left,right]
    bool tree[N<<2]; // 记录区间[left,right]的村庄是否全都未被摧毁
    

    首先构建线段树

    每一个结点的意义是[left,right]区间的村庄是否全未被摧毁

    显然[left,right] = true 当且仅当 [left,mid] = true && [mid+1,right] = true

    void build_tree(int left,int right,int root){
        tree[root] = true; // 标记这段区间的村庄未被摧毁
        get_lr[root].first = left; 
        get_lr[root].second = right; // 记录当前结点所维护的区间[left,right]
        
        if(left == right){ 
            find_leaf[left] = root; // 记录叶子结点
        }else{
            int mid = (left+right)>>1;
            build_tree(left,mid,root<<1);
            build_tree(mid+1,right,root<<1|1);
        }
    }
    

    2.摧毁操作

    首先,每一个村庄p代表一个叶节点[p,p].

    摧毁一个村庄,首先会更新对应的叶结点为false,然后还需要更新其父结点为false

    void destroy(int root){
        while(tree[root]){ // 如果当前结点未true则需要更新,如果未false则可以终止(再往后全都是false)
            tree[root] = false;
            root>>=1; // 不用死循环的问题,因为tree[0] == false,会自动中跳出
        }
    }
    

    3.重建操作

    重建一个村庄,首先更新叶结点; 如果兄弟结点也为true,则更新父结点为true

    返回兄弟结点函数

    int find_bro(int root){
        if(root & 1){
            return root ^ 1;
        }else{
            return root | 1;
        }
    }
    

    重建函数

    void rebuild(int root){
        while(true){
            tree[root] = true;
            if(tree[find_bro(root)]){ // 如果兄弟为true才更新父结点为true
                root >>= 1;
            }else{
                break; // 不用死循环的问题,因为tree[0] == false,会自动中跳出
            }
        }
    }
    

    4.查询操作

    当时在查询操作上卡了好久,一直想着如何判断左端点和右端点.后来发现,只需要找到两个点

    1. 在[1,pos]区间上最靠右的被摧毁的村庄lf (left false)
    2. 在[pos,n]区间上最靠左的被摧毁的村庄rf (right false)
    image-20201030154009525

    首先实现寻找lf的函数

    int find_left_false(int pos,int root){ // 在[1,pos]区间中找到最靠右的false点
        if(tree[root]){
            return get_lr[root].first-1; // 如果整个区间都是true,那么返回区间左端点-1 
            
        }else if(get_lr[root].first == get_lr[root].second){ // 访问到了叶结点,则这棵树中最靠右的false点为区间左端点
            return get_lr[root].first;
            
        }else{
            int mid = (get_lr[root].first + get_lr[root].second) >> 1;
            if(pos <= mid){ // 舍弃右子树,因为右子树不在区间[1,pos]内
                return find_left_false(pos,root<<1); 
            }else{ // 左右都需要考虑,优先考虑右子树,因为右子树可能存在更靠右的false结点
                int a = find_left_false(pos,root<<1|1);
                if(a == get_lr[root<<1|1].first - 1){ // 右子树全为true
                    a = find_left_false(pos,root<<1);
                }
                return a;
            }
        }
    }
    

    实现寻找rf的函数

    int find_right_false(int pos,int root){
        if(tree[root]){
            return get_lr[root].second+1;
        }else if(get_lr[root].first == get_lr[root].second){
            return get_lr[root].first;
        }else{
            int mid = (get_lr[root].first + get_lr[root].second) >> 1;
            if(pos > mid){ // 舍弃左子树
                return find_right_false(pos,root<<1|1);
            }else{ // 左右都需要考虑,优先考虑左子树
                int a = find_right_false(pos,root<<1);
                if(a == get_lr[root<<1].second + 1){ // 左子树全为true
                    a = find_right_false(pos,root<<1|1);
                }
                return a;
            }
        }
    }
    

    完整代码

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    #define N 50000+5
    #define MIN(a,b) (a<b?a:b)
    #define MAX(a,b) (a>b?a:b)
    typedef pair<int,int> pii;
    
    
    int stk_ptr = 0;
    int stk[N];
    char cmd[5];
    int find_leaf[N]; // 根据位置访问其代表的叶子结点的序号
    pii get_lr[N<<2]; // 获得当前结点所代表的区间[left,right]
    bool tree[N<<2]; // 记录一段区间[left,right]是否都是互通的
    
    
    void build_tree(int left,int right,int root){
        tree[root] = true; 
        get_lr[root].first = left;
        get_lr[root].second = right;
        if(left == right){
            find_leaf[left] = root;
        }else{
            int mid = (left+right)>>1;
            build_tree(left,mid,root<<1);
            build_tree(mid+1,right,root<<1|1);
        }
    }
    
    int find_bro(int root){
        if(root & 1){
            return root ^ 1;
        }else{
            return root | 1;
        }
    }
    
    void destroy(int root){
        while(tree[root]){
            tree[root] = false;
            root>>=1;
        }
    }
    
    void rebuild(int root){
        while(true){
            tree[root] = true;
            if(tree[find_bro(root)]){ 
                root >>= 1;
            }else{
                break; 
            }
        }
    }
    
    int find_left_false(int pos,int root){ // 在[1,pos]区间中找到最大的false点
        if(tree[root]){
            return get_lr[root].first-1;
        }else if(get_lr[root].first == get_lr[root].second){
            return get_lr[root].first;
        }else{
            int mid = (get_lr[root].first + get_lr[root].second) >> 1;
            if(pos <= mid){ // 舍弃右子树
                return find_left_false(pos,root<<1);
            }else{ // 左右都需要考虑,优先考虑右子树
                int a = find_left_false(pos,root<<1|1);
                if(a == get_lr[root<<1|1].first - 1){ // 右子树全为true
                    a = find_left_false(pos,root<<1);
                }
                return a;
            }
        }
    }
    
    int find_right_false(int pos,int root){
        if(tree[root]){
            return get_lr[root].second+1;
        }else if(get_lr[root].first == get_lr[root].second){
            return get_lr[root].first;
        }else{
            int mid = (get_lr[root].first + get_lr[root].second) >> 1;
            if(pos > mid){ // 舍弃左子树
                return find_right_false(pos,root<<1|1);
            }else{ // 左右都需要考虑,优先考虑左子树
                int a = find_right_false(pos,root<<1);
                if(a == get_lr[root<<1].second + 1){ // 左子树全为true
                    a = find_right_false(pos,root<<1|1);
                }
                return a;
            }
        }
    }
    int main(){
        int n,m;
        while(scanf("%d%d",&n,&m) != EOF){
            stk_ptr = 0;
            int temp = 0;
            build_tree(1,n,1);
            while(m--){
                scanf("%s",cmd);
                if(cmd[0] == 'D'){
                    scanf("%d",&temp);
                    stk[stk_ptr++] = temp;
                    destroy(find_leaf[temp]);
                }else if(cmd[0] == 'R'){
                    temp = stk[--stk_ptr];
                    rebuild(find_leaf[temp]);
                }else{
                    scanf("%d",&temp);
                    if(tree[find_leaf[temp]] == false){
                        printf("0
    ");
                    }else{
                        int lf = find_left_false(temp,1);
                        int rf = find_right_false(temp,1);
                        printf("%d
    ",(rf-1) - (lf+1) + 1);
                    }
                }
            }
        }
        return 0;
    }
    
    ---- suffer now and live the rest of your life as a champion ----
  • 相关阅读:
    yii1.0 yii2.0 ar
    php 函数
    整除理论,1.1数的整除性定理总结
    设M=5^2003+7^2004+9^2005+11^2006,求证8|M。(整除理论,1.1.8)
    已知整数m,n,p,q适合(m-p)|(mn+pq)证明:(m-p)|(mq+np)(整除理论1.1.5)
    证明:一个整数a若不能被6整除,则a2+24必能被24整除。(整除理论,1.1.4)
    ural 1073.Square Country(动态规划)
    10个男孩和n个女孩共买了n2+8n+2本书,已知他们每人买的书本的数量是相同的,且女孩人数多于南海人数,问女孩人数是多少?(整除原理1.1.3)
    设正整数n的十进制表示为n=ak……a1a0(0<=ai<=9,0<=i<=k,ak!=0),n的个位为起始数字的数字的正负交错之和T(n)=a0+a1+……+(-1)kak,证明:11|n的充分必要条件是11|T(n);(整除理论1.1.2))
    设n是奇数,证明:16|(n4+4n2+11)(整除原理1.1.1)
  • 原文地址:https://www.cnblogs.com/popodynasty/p/13902587.html
Copyright © 2011-2022 走看看