zoukankan      html  css  js  c++  java
  • 线段树初步(2)

    今天学点更难的。

    1.区间修改

    区间修改,就是修改一整段的值。第一种方法是进行循环单点修改,但是这样复杂度显然太大……为了能节省复杂度,我们有一种方法,就是使用lazy标记,我们修改一个区间的时候,并不需要把区间中的所有值即刻修改,只要在访问的时候修改即可。那么我们对于修改的区间打上lazy标记,记录要修改的值即可。

    要注意一下!如果是求和的话一定要在下放lazy标记的时候乘以区间长度!(题目数据太水没意识到这点的蒟蒻泪流满面……)不过取最大最小值不需要。

    看一下代码

    void down(int x,int l,int r)//区间修改无需每次都修改到叶节点,所以我们用lazy标记记录延迟修改 
    {
      int mid = (l+r) >> 1;
    if(lazy[x]) { tree[x<<1] += lazy[x] * (mid-l+1); tree[x<<1|1] += lazy[x] * (r-mid); lazy[x<<1] += lazy[x]; lazy[x<<1|1] += lazy[x]; lazy[x] = 0; } } void add(int p,int l,int r,int kl,int kr,int val) { if(kl == l && kr == r) { tree[p] = val*(r-l+1); lazy[p] = val; return; } down(p,l,r); int mid = (l+r) >> 1; if(kr <= mid) add(p<<1,l,mid,kl,kr,val); else if(kl > mid) add(p<<1|1,mid+1,r,kl,kr,val); else { add(p<<1,l,mid,kl,mid,val); add(p<<1|1,mid+1,r,mid+1,kr,val); } tree[p] = tree[p<<1] + tree[p<<1|1]; }//区间修改,当区间完全覆盖的时候直接修改,否则先下放标记之后二分修改 //注意修改的时候,整个区间的值要修改为乘积,但是lazy标记只是改变的值即可

    要注意修改其实有两种方法,第一种是严格的划分,必须kl==l&&kr==r才修改。这种修改需要把访问的区间分割。(我称之为精准扶贫)

    第二种方法就是不改变需要访问的区间,只要当前区间被包含在访问区间中就直接累加它的值。这种方法在递归的时候条件不同,只要kl<=mid | | kr>mid就直接在新的区间之内计算即可。(代码在线段树初步(1)中有)

    注意区间修改等等不只局限于求和,还有取min/max之类的操作,上道例题试一下。

    售票系统

    【问题描述】

    某次列车途经C个城市,城市编号依次为1C,列车上共有S个座位,铁路局规定售出的车票只能是坐票,即车上所有的旅客都有座,售票系统是由计算机执行的,每一个售票申请包含三个参数,分别用ODN表示,O为起始站,D为目的地站,N为车票张数,售票系统对该售票申请作出受理或不受理的决定,只有在从OD的区段内列车上都有N个或N个以上的空座位时该售票申请才被受理,请你写一个程序,实现这个自动售票系统。

    【输入格式】

    第一行包含三个用空格隔开的整数CSR,其中1<=C<=600001<=S<=600001<=R<=60000C为城市个数,S为列车上的座位数,R为所有售票申请总数。接下来的R行每行为一个售票申请,用三个由空格隔开的整数O,DN表示,O为起始站,D为目的地站,N为车票张数,其中1<=0<D<=C1<=N<=S,所有的售票申请按申请的时间从早到晚给出。

    【输出格式】

    共有R行,每行输出一个“YES”或“NO”,表示当前的售票申请被受理或不被受理。

    【输入样例】

    4 6 4

    1 4 2

    1 3 2

    2 4 3

    1 2 3

    【输出样例】

    YES

    YES

    NO

    NO

    这道题我们把城市之间的路看成一条条线段,那么我们的任务就是用一棵线段树维护每条路上的车票是否够用。因为我们要保证每条都够用所以必须在所有节点中取最小值,与需求值比较即可。

    上代码看一下。

    #include <cstdio>
    #include <iostream>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    typedef long long ll;
    const int N = 100005;
    int c,s,r; 
    struct Seg
    {
        int v,lazy;
    }t[N<<2];
    int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            ans *= 10;
            ans += ch - '0';
            ch = getchar();
        }
        return ans * op;
    }
    
    void pushdown(int p)
    {
        if(t[p].lazy)
        {
            t[p<<1].v += t[p].lazy;
            t[p<<1|1].v += t[p].lazy;
            t[p<<1].lazy += t[p].lazy;
            t[p<<1|1].lazy += t[p].lazy;
            t[p].lazy = 0;
        }
    }
    void build(int p,int l,int r)
    {
        if(l == r) 
        {
            t[p].v = s;
            return;    
        }
        int mid = (l+r)>>1;
        build(p<<1,l,mid);
        build(p<<1|1,mid+1,r);
        t[p].v = min(t[p<<1].v,t[p<<1|1].v);//建造的时候即取小
    }
    int query(int p,int l,int r,int kl,int kr)
    {
        if(kl == l && kr == r) return t[p].v;
        pushdown(p);
        int mid = (l+r) >> 1;
        if(kr <= mid) return query(p<<1,l,mid,kl,kr);
        else if(kl > mid) return query(p<<1|1,mid+1,r,kl,kr);
        else return min(query(p<<1,l,mid,kl,mid),query(p<<1|1,mid+1,r,mid+1,kr));
    }//使用的是精准扶贫的分割方法
    void modify(int p,int l,int r,int kl,int kr,int val)
    {
        if(kl == l && kr == r)
        {
            t[p].v += val,t[p].lazy += val;
            return;
        }
        pushdown(p);
        int mid = (l+r) >> 1;
        if(kr <= mid) modify(p<<1,l,mid,kl,kr,val);
        else if(kl > mid) modify(p<<1|1,mid+1,r,kl,kr,val);
        else modify(p<<1,l,mid,kl,mid,val),modify(p<<1|1,mid+1,r,mid+1,kr,val);//同样是精准扶贫
        t[p].v = min(t[p<<1].v,t[p<<1|1].v);//每次修改返回的时候需要更新
    }
    int main()
    {
        c = read(),s = read(),r = read();
        build(1,1,c);
        while(r--)
        {
            int o,d,n;
            o = read(),d = read(),n = read();
            d--;//因为我们要计算的是区间,所以-- 
            if(query(1,1,c,o,d) >= n)
            {
                printf("YES
    ");
                modify(1,1,c,o,d,-n);//因为座位变少所以--
            }
            else printf("NO
    ");
        }
        return 0;
    }

    2.区间合并

    蒟蒻的智商要不够用了!这个东西感觉有点难理解……

    我们有时要求最长连续区间是多少,那我们怎么用线段树维护呢?

    在建树的时候我们把节点可用区间长度,其左子树可用区间长,右子树可用区间长都设置为其控制的范围长(递归建树的话就是r-l+1)。之后其实区间修改是最相似的,主要不同在于下放标记,返回修改和查询值。

    先说下放标记。lazy标记此时有几种状态,可以分别代表未使用,占用和未占用(未占用和未使用是两个概念)也可以有更多的情况。如果他不是未使用,那么我们在下放标记的时候就把其左右儿子的状态设为与之相同。如果是占用,那么其左右子全部可用的区间长度都为0,如果不占用,那么就修改为区间的长度的一半。(这段似乎很难理解……蒟蒻在这里卡壳了……)

    返回时候的修改呢?咋修改?这段看代码的注释吧,不过一定要取max。

    查询值的话……这次使用的是包含法的查询。注意可能出现的区间位置有3,一是全在左子树中,一是全在右子树中,另一种是左子树的右区间加上右子树的左区间。

    算法其实就这么多,但说句实话不好理解。

    看一道例题,上一下代码吧。

    HotelN1 N 50,000)间rooms,并且所有的rooms都是连续排列在同一边,groups需要check in 房间,要求房间的编号为连续的r..r+Di-1并且r是最小的;visitors同样可能check out,并且他们每次check out都是编号为Xi ..Xi +Di-1 (1 Xi N-Di+1)的房间,题目的输入有两种样式:

    1  a     :  groups需要check in  a间编号连续的房间

    2  a   b visitors  check out 房间,其中房间编号是 aa+b-1

    要求对于每次request,输出为groups分配数目为a的房间中编号最小的房间编号

    【输入格式】

    Line 1: 两个整数: N  M

    Lines 2..M+1: (a) check-in 查询: 1  Di ,表示需要Di个房间;

    (b) check-out: 2  Xi  Di ,表示退掉从Xi开始的Di个房间;

    【输出格式】

    对于每次check in 查询,若能满足,输出可分配的房间最小号码,否则输出0

    【输入样例】10 6

    1 3

    1 3

    1 3

    1 3

    2 5 5

    1 6

    【输出样例】

    1

    4

    7

    0

    5

    (题目描述有点病请见谅)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cstdlib>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    using namespace std;
    const int M = 100005;
    struct seg
    {
        int lazy,lsum,msum,rsum;
    }t[M<<2];
    //lazy:-1表示当前没有覆盖标记,1表示均覆盖为不可行,0表示均覆盖为可行 
    //lsum:该区间从左起连续的可用区间长度的最大值 
    //msum:该区间中连续的可用区间长度的最大值 
    //rsum:该区间从右起连续的可用区间长度的最大值 
    int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            ans *= 10;
            ans += ch - '0';
            ch = getchar();
        }
        return ans * op;
    }
    void pushup(int p,int m)//m表示区间的长度 
    {
        t[p].lsum = t[p<<1].lsum,t[p].rsum = t[p<<1|1].rsum; 
        if(t[p].lsum == m-(m>>1)) t[p].lsum += t[p<<1|1].lsum;
        /*如果左孩子全部为可用区间,那么加上右孩子的左端
        因为m是要修改的区间的长度,t[p].lsum == m-(m>>1)说明左孩子全部可用,那么就加上右孩子的左端*/
        if(t[p].rsum == m>>1) t[p].rsum += t[p<<1].rsum; /*同上*/
        t[p].msum = max(max(t[p<<1].msum,t[p<<1|1].msum),t[p<<1].rsum + t[p<<1|1].lsum);
        /*该区间的可用区间可能是:左孩子最大的可用区间、右孩子最大的可用区间,和跨越左右孩子加在一起的可用区间*/
    }
    
    void pushdown(int p,int m) 
    {
        if(t[p].lazy != -1)//表示还没有动过 
        {
            t[p<<1].lazy = t[p<<1|1].lazy = t[p].lazy;
            if(t[p].lazy == 1)//如果处于占用状态 
            {
                t[p<<1].msum = t[p<<1].lsum = t[p<<1].rsum = 0;
                t[p<<1|1].msum = t[p<<1|1].lsum = t[p<<1|1].rsum = 0;
                //那么这个节点的可用区间和它的左右孩子的可用区间全部为0 
            }
            else//表示处于空闲状态 
            {
                t[p<<1].msum = t[p<<1].lsum = t[p<<1].rsum = m-(m>>1);
                t[p<<1|1].msum = t[p<<1|1].lsum = t[p<<1|1].rsum = m>>1;
                //那么就把这个节点的可用区间长度修改为线段长度的左右两半 
            }
            t[p].lazy = -1; 
            /*千万不要忘记将rt清为-1*/ 
        }
    }
    
    int query(int w,int l,int r,int p)
    {
        if(l == r) return l;
        pushdown(p,r-l+1);
        int mid = (l + r) >> 1;
        if(t[p<<1].msum >= w) return query(w,l,mid,p<<1);/*由于要找最左边的区间,按照左孩子、跨越两者、有孩子的顺序查找*/
        if(t[p<<1].rsum + t[p<<1|1].lsum >= w) return mid - t[p<<1].rsum + 1;
        return query(w,mid+1,r,p<<1|1);
    }
    
    void update(int kl,int kr,int o,int l,int r,int p)//o表示当前是开房还是退房 
    {
        if(kl <= l && kr >= r)
        {
            t[p].lazy = o;
            if(o == 1) t[p].msum = t[p].lsum = t[p].rsum = 0;
            else t[p].msum = t[p].lsum = t[p].rsum = r-l+1;
            return;
        }
        pushdown(p,r-l+1);//这里是l和r,不要写成kl和kr 
        int mid = (l+r) >> 1;
        if (kl <= mid) update(kl,kr,o,l,mid,p<<1); 
        if (kr > mid)  update(kl,kr,o,mid+1,r,p<<1|1);
        pushup(p,r-l+1);
    }
    
    void build(int l,int r,int p)
    {
        t[p].msum = t[p].lsum = t[p].rsum = r-l+1;//一开始能使用的区间长度都是其所控制的区间长 
        t[p].lazy = -1;
        if (l == r) return;
        int mid = (l+r) >> 1;
        build(l,mid,p<<1);
        build(mid+1,r,p<<1|1);
    }
    int n,m,op;
    int main()
    {
        n = read(),m = read();
        build(1,n,1);
        rep(i,0,m-1)
        {
            op = read();
            if(op == 1)//开房 
            {
                int w = read();//代表要开房的数量 
                if (t[1].msum < w) printf("0
    "); /*如果根的可用区间已经小于w,那么一定是找不到长度为w的可用区间*/
                else
                {
                    int p = query(w,1,n,1);
                    printf("%d
    ",p);
                    update(p,p+w-1,1,1,n,1);//从p开始的房间被占用 
                }
            }
            else//退房 
            {
                int u = read();int v = read();
                update(u,u+v-1,0,1,n,1);
            }
        }
        return 0;
    }
    /*
    10 6
    1 3
    1 3
    1 3
    1 3
    2 5 5
    1 6
    */
  • 相关阅读:
    Expression Blend4经验分享:自适应布局浅析
    Expression Blend4经验分享:制作一个简单的图片按钮样式
    Expression Blend4经验分享:制作一个简单的文字按钮样式
    24.Unity手势操作(转自宣雨松博客)一
    1.关于Unity -Vuforia -Android 开发 ,平台的搭建(极品菜鸟完整版)
    26. Kinect + Unity 体感及增强现实开发历程一
    25.Unity3D手机中Input类touch详解-Unity触屏事件解析到底(Twisted Fate)
    23.Unity+Metaio SDK 开发笔记
    徐CC , Unity +C# 自学笔记2
    unity3d中的http通信
  • 原文地址:https://www.cnblogs.com/captain1/p/8998388.html
Copyright © 2011-2022 走看看