线段树初步(2)

今天学点更难的。

1.区间修改

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

看一下代码

void down(int x)//区间修改无需每次都修改到叶节点,所以我们用lazy标记记录延迟修改 
{
    if(lazy[x])
    {
        tree[x<<1] += lazy[x];
        tree[x<<1|1] += lazy[x];
        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);
    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('\n')

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\n");
            modify(1,1,c,o,d,-n);//因为座位变少所以--
        }
        else printf("NO\n");
    }
    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('\n')
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\n"); /*如果根的可用区间已经小于w,那么一定是找不到长度为w的可用区间*/
            else
            {
                int p = query(w,1,n,1);
                printf("%d\n",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
*/

猜你喜欢

转载自www.cnblogs.com/captain1/p/8998388.html