线段树 (成段更新)

 hdu1698

 lazy-tag(lazy标记)进行区间修改:

  为了减小修改次数,先将要进行的修改存起来,当访问到子节点的时候,如果发现父节点带有标记,就顺便将子节点更新,就像搭顺风车一样。在这道题中,每次可能要将一个区间更改为一个值,比如全集为[1,7],现在要对区间[1,4]进行操作,例如要将[1,4]更改为1,如果采用单点更新,那么就要访问到叶节点一个一个第更新。实际上,当我们从[1,7]访问到它的左儿子[1,4]的时候,由于此时[1,4]恰好已经覆盖了要更新的区间,于是就将[1,4].sum = 1 * (4 - 1 + 1),当下次需要访问[1,4]的子节点时,发现[1,4]带有标记,就在访问[1,4]的子节点的时候顺便将以前记下的1操作,对[1,4]的子节点进行实施,同时将[1,4]的子节点带上标记。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

#define lson l,mid,x<<1
#define rson mid+1,r,x<<1|1
const int MAX = 444444;

struct node
{
    int vis;//标记当前节点是否被更新
    int sum;//当前区间的总价值
}a[MAX];

void Pushup(int x)
{
    a[x].sum = a[x<<1].sum + a[x<<1|1].sum;
}

void build(int l,int r,int x)
{
    a[x].vis = 0;//初始化时所有的节点都未被更新
    a[x].sum = 1;
    if(l==r)return ;
    int mid = (l+r)>>1;
    build(lson);
    build(rson);
    Pushup(x);
}

void Pushdown(int x,int m)//对当前节点的子节点进行更新
{
    if(a[x].vis!=0)
    {
        a[x<<1].vis = a[x<<1|1].vis = a[x].vis;
        a[x<<1].sum = (m - (m>>1)) * a[x].vis;
        a[x<<1|1].sum = (m>>1) * a[x].vis;
        a[x].vis = 0;
    }
}

void update(int A,int B,int C,int l,int r,int x)//对线段树进行更新
{
    if(A<=l && B>=r)//如果覆盖整个区间则将整段区间的值修改为C,区间价值即为C*(r - l + 1)
    {
        a[x].vis = C;
        a[x].sum = C * (r - l + 1);
        return ;
    }
    Pushdown(x,r - l + 1);//更新儿子节点
    int mid = (l + r)>>1;
    if(A<=mid) update(A,B,C,lson);//有覆盖到左儿子
    if(B > mid) update(A,B,C,rson);//有覆盖到右儿子
    Pushup(x);
}

int main()
{
    int t,n,m,x,y,z,temp = 1;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        build(1,n,1);
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d%d%d",&x,&y,&z);
            update(x,y,z,1,n,1);
        }
        cout<<"Case "<<temp++<<": The total value of the hook is "<<a[1].sum<<"."<<endl;
    }
    return 0;
}

poj 3468

  题意:给一些数,有两种操作。一种是某个区间都增加一个数,另一种是查询某段区间的和。

  思路:在这道题中,每次可能要将一个区间更改为一个值,

       比如全集为[1,7],现在要对区间[1,4]进行操作,

       例如要将[1,4]之间的值都加上1,如果采用单点更新,

       那么就要访问到叶节点一个一个第更新。

       实际上,当我们从[1,7]访问到它的左儿子[1,4]的时候,

       由于此时[1,4]恰好已经覆盖了要更新的区间,

       于是就将[1,4].sum = 1 * (4 - 1 + 1),并将[1,4].vis += 1,

       当下次需要访问[1,4]的子节点时,发现[1,4]带有标记,

       就在访问[1,4]的子节点的时候顺便将以前记下的vis操作,

       对[1,4]的子节点进行实施,同时将[1,4]的子节点带上标记。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

#define lson l,mid,x<<1
#define rson mid+1,r,x<<1|1
#define LL long long
const LL MAX = 444444;

struct node
{
    LL vis;//标记是否进行过修改
    LL sum;//当前区间的和
}a[MAX];

void Pushup(LL x)
{
    a[x].sum = a[x<<1].sum + a[x<<1|1].sum;
}

void Pushdown(LL x,LL m)//更新当前节点并修改左右子节点
{
    if(a[x].vis)
    {
        a[x<<1].vis += a[x].vis;
        a[x<<1|1].vis += a[x].vis;//这里注意要用“+=”,因为有可能进行过多次修改,如果没累加的话,记录的只是访问之前的最后一次修改
        a[x<<1].sum += (m - (m>>1)) * a[x].vis;
        a[x<<1|1].sum += (m>>1) * a[x].vis;
        a[x].vis = 0;
    }
}

void build(LL l,LL r,LL x)
{
    a[x].vis = 0;
    a[x].sum = 0;
    if(l==r) return ;
    LL mid = (l+r)>>1;
    build(lson);
    build(rson);
}

void update(LL A,LL B,LL C,LL l,LL r,LL x)//对区间【A,B】进行更新
{
    if(A<=l && B>=r)//完全覆盖区间【r,l】,返回区间的和即可
    {
        a[x].sum += (r - l + 1) * C;
        a[x].vis += C;
        return ;
    }
    Pushdown(x,r - l +1);//需要对节点x进行操作,所以要先修改x节点
    LL mid = (l + r)>>1;
    if(A<=mid) update(A,B,C,lson);//有覆盖到左区间
    if(B > mid) update(A,B,C,rson);//有覆盖到右区间
    Pushup(x);
}

LL query(LL A,LL B,LL l,LL r,LL x)//查找区间【A,B】的和
{
    if(A<=l && B>=r) return a[x].sum;
    Pushdown(x,r - l + 1);
    LL mid = (l + r)>>1;
    LL ret = 0;
    if(A<=mid) ret += query(A,B,lson);
    if(B > mid) ret += query(A,B,rson);
    return ret;
}

int main()
{
    LL n,q,i,k;
    while(~scanf("%lld%lld",&n,&q))
    {
        build(1,n,1);
        for(i = 1; i <= n; i++)
        {
            scanf("%lld",&k);
            update(i,i,k,1,n,1);
        }
        char ch;
        LL A,B,C;
        while(q--)
        {
            getchar();
            scanf("%c",&ch);
            if(ch=='Q')
            {
                scanf("%lld%lld",&A,&B);
                printf("%lld\n",query(A,B,1,n,1));
            }
            else if(ch=='C')
            {
                scanf("%lld%lld%lld",&A,&B,&C);
                update(A,B,C,1,n,1);
            }
        }
    }
    return 0;
}

poj 2528

  题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报

      (这题数据范围很大,直接搞超时+超内存,需要离散化:

      离散化简单的来说就是只取我们需要的值来用,

      比如说区间[1000,2000],[1990,2012]

      我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,

      所以我只需要1000,1990,2000,2012就够了,

      将其分别映射到0,1,2,3,这样复杂度就大大的降下来了

      所以离散化要保存所有需要用到的值,

      排序后,分别映射到1~n,这样复杂度就会小很多很多)

  思路:用到线段树,因为最多有10^6,

      建那么大的树肯定会爆空间和时间,

      所以想到要离散化,将每条线段的两端,

      就是海报的两头分开考虑,所有的端点放在一起非递减排序,

      去掉相同的,计算出有n个不同的点,编号从1到n,——这是离散化过程。

      然后建二叉树。更新的时候先二分查找那条线段两端的点的编号,

      然后在那棵建好的树上更新,遇到一样的节点就覆盖,

      如果线段在那个节点的子节点上,而且这个节点又被其他线段覆盖过,

      那么就要更新它的子节点,他自己改成不被覆盖的。

      最后计算结果那里要用个辅助数组,从根节点开始找被覆盖的节点,

       分别将不同的值放在数组里,最后看有几个结果就是几个。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

#define lson l,mid,x<<1
#define rson mid+1,r,x<<1|1

const int MAX = 11111;
int a[MAX<<4];
int ans;
bool hash[MAX<<4];

struct node1
{
    int l,r;
}m[MAX];

void Pushdown(int x)
{
    if(a[x]!=-1)//如果当前节点已被覆盖,则更新他的左右儿子节点,并将该节点改为没被覆盖
    {
        a[x<<1] = a[x];
        a[x<<1|1] = a[x];
        a[x] = -1;
    }
}

void Update(int A,int B,int C,int l,int r,int x)
{
    if(A<=l && B>=r)
    {
        a[x] = C;
        return ;
    }
    Pushdown(x);
    int mid = (l + r)>>1;
    if(A<=mid) Update(A,B,C,lson);
    if(B > mid) Update(A,B,C,rson);
}

void query(int l,int r,int x)
{
    if(a[x]!=-1)
    {
        if(!hash[a[x]]) ans++;
        hash[a[x]] = true;
        return ;
    }
    if(l==r)return ;
    int mid = (l + r)>>1;
    query(lson);
    query(rson);
}

int bin(int key,int n,int ma[])//离散化,用二分查找
{
    int l = 0;
    int r = n-1;
    while(l<=r)
    {
        int mid = (l + r)>>1;
        if(ma[mid]==key) return mid;
        if(ma[mid] < key) l = mid + 1;
        else r = mid - 1;
    }
    return -1;
}

int main()
{
    int t,n,i;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        int ma[MAX<<1];
        int nu = 0;
        for(i = 0; i < n; i++)
        {
            scanf("%d%d",&m[i].l,&m[i].r);
            ma[nu++] = m[i].l;
            ma[nu++] = m[i].r;
        }
        sort(ma,ma+nu);
        int nu1 = 1;
        for(i = 1; i < nu; i++)//去除重复的数
        if(ma[i]!=ma[i-1]) ma[nu1++] = ma[i];
        memset(a,-1,sizeof(a));
        for(i = 0; i < n; i++)
        {
            int l = bin(m[i].l,nu1,ma);
            int r = bin(m[i].r,nu1,ma);//l和r是离散化后所对应的值
            Update(l,r,i,0,nu1-1,1);//更新区间【l,r】
        }
        ans = 0;
        memset(hash,false,sizeof(hash));
        query(0,nu1-1,1);
        printf("%d\n",ans);
    }
    return 0;
}

poj 3225

  题意:定义对集合的交、并、差、异或,求空集经过一系列操作后的结果。

  题解:将原点集每一个点乘以2,形成一个新的点集,

       其中偶数点都对应着原来的点,

       奇数点对应着不包括它左右两个点的开区间,

       即2k+1==>(k,k+1),

       于是区间上所有点都能用整点表示,

       线段树可求解,对于每一个操作区间,

       无论开闭,都对应这线段树上的一段线段。

  1、并运算[a,b],就是将[a,b]赋值为1.(a,b均为对应之后的点)

  2、交运算[a,b],将除了[a,b]区间以外的线段清0.

  3、S-[a,b],将[a,b]区间清0.

  4、[a,b]-S,将[a,b]区间以外线段清0,并且将[a,b]线段所代表区域取反.

  5、异或运算,上面两者去并,就是将[a,b]区域取反.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

#define lson l,mid,x<<1
#define rson mid+1,r,x<<1|1

const int MAX = 222222;
bool hash[MAX];

struct node
{
    int Xor,cover;//用0和1表示是否包含区间,-1表示该区间内既有包含又有不包含
}a[MAX<<2];

void FXor(int x)
{
    if(a[x].cover!=-1)
    a[x].cover ^= 1;
    else a[x].Xor ^= 1;
}
/*成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,
  我们可以称之为异或操作。
  很明显我们可以知道这个性质:
      当一个区间被覆盖后,不管之前有没有异或标记都没有意义了。
  所以当一个节点得到覆盖标记时把异或标记清空,
  而当一个节点得到异或标记的时候,
  先判断覆盖标记,如果是0或1,
  直接改变一下覆盖标记,不然的话改变异或标记
*/
void Pushdown(int x)
{
    if(a[x].cover!=-1)//有覆盖标记
    {
        a[x<<1].cover = a[x<<1|1].cover = a[x].cover;
        a[x<<1].Xor = a[x<<1|1].Xor = 0;//清空标记
        a[x].cover = -1;
    }
    if(a[x].Xor)//有异或标记
    {
        FXor(x<<1);
        FXor(x<<1|1);
        a[x].Xor = 0;
    }
}

void Update(char ch,int A,int B,int l,int r,int x)
{
    if(A<=l && B>=r)
    {
        if(ch=='U')
        {
            a[x].cover = 1;
            a[x].Xor = 0;
        }
        else if(ch=='D')
        {
            a[x].cover = 0;
            a[x].Xor = 0;
        }
        else if(ch=='C' || ch=='S')
        {
            FXor(x);
        }
        return ;
    }
    Pushdown(x);
    int mid = (l + r)>>1;
    if(A<=mid) Update(ch,A,B,lson);
    else
    {
        if(ch=='I' || ch=='C')
        {
            a[x<<1].cover = a[x<<1].Xor = 0;
        }
    }
    if(B > mid) Update(ch,A,B,rson);
    else
    {
        if(ch=='I' || ch=='C')
        {
            a[x<<1|1].cover = a[x<<1|1].Xor = 0;
        }
    }
}

void query(int l,int r,int x)
{
    if(a[x].cover==1)
    {
        for(int i = l; i <= r; i++)
        hash[i] = true;
        return ;
    }
    else if(a[x].cover==0)return ;
    Pushdown(x);
    int mid = (l + r)>>1;
    query(lson);
    query(rson);
}

int main()
{
    a[1].cover = a[1].Xor = 0;
    char ch,l,r;
    int A,B;
    while(~scanf("%c %c%d,%d%c\n",&ch,&l,&A,&B,&r))
    {
        /*离散化处理 
          线段树经常碰见的离散化问题。
          对于这种经典的,要把常规思维中的点变成线的问题,
          办法,就是将每个点倍增,这意味着,例如我们存储了{ 1, 2, 3, 4, 5}
          代表了我们拥有的是(0,3),分开来讲,就是
          (0, 1)、1、(1, 2)、2、(2, 3)。
        */
        A<<=1;
        B<<=1;
        if(l=='(')A++;
        if(r==')')B--;
        /*还有一点需要注意,输入中的区间(a, b),由于a <= b,
          可能出现类似(2, 2)这样的区间,应看成空。
        */
        if(A > B)
        {
            if(ch=='I' || ch=='C')
            a[1].cover = a[1].Xor = 0;
        }
        else Update(ch,A,B,0,MAX,1);
    }
    query(0,MAX,1);
    bool flag = false;
    int e,s = -1;
    for(int i = 0; i <= MAX; i++)
    {
        if(hash[i]==1)
        {
            if(s==-1)
            s = i;
            e = i;
        }
        else
        {
            if(s!=-1)
            {
                if(flag) printf(" ");
                flag = true;
                if(s&1)
                printf("(");
                else printf("[");
                printf("%d,%d",s>>1,(e+1)>>1);
                if(e&1)
                printf(")");
                else printf("]");
                s = -1;
            }
        }
    }
    if(!flag) printf("empty set");
    puts("");
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值