线段树练习——成段更新

这一块关键理解延迟标记(或者说懒惰标记)lz[],就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新或者询问到的时候再更新。

这里主要包括两个方面:

1:成端替换; 2:成端累加(累减);

hdu 1698 http://acm.hdu.edu.cn/showproblem.php?pid=1698

题意:

给定n个连续的奖牌(每个奖牌都有一个价值),初始都为铜牌。有q个操作X,Y,Z,将区间[x,y]内的奖牌的价值改为Z,问经过q个操作后总的价值为多少。

思路:

就是典型的成段替换,lz标记,关键是注意将lz   pushdown。。

View Code
#include<iostream>

#include<cstring>

#include <cstdio>

#define maxn 100007

using namespace std;

 

int val[4*maxn];

int lz[4*maxn];

void pushup(int rt)

{

    val[rt] = val[rt<<1] + val[rt<<1|1];

}

void pushdown(int rt,int m)

{

    if (lz[rt])

    {

        lz[rt<<1] = lz[rt<<1|1] = lz[rt];

        val[rt<<1] = (m - (m>>1))*lz[rt];//左边是[1,m/2]有m-m/2个

        val[rt<<1|1] = (m>>1)*lz[rt];//右边[m/2+1,m]有m/2个

        lz[rt] = 0;

    }

}

 

void build(int l,int r,int rt)

{

    lz[rt] = 0;

    if (l == r)

    {

        val[rt] = 1;

        return ;

    }

    int m = (l + r)>>1;

    build(l,m,rt<<1);

    build(m + 1,r,rt<<1|1);

    pushup(rt);

}

 

void update(int L,int R,int sc,int l,int r,int rt)

{

    if (l >= L && r <= R)

    {

        lz[rt] = sc;

        val[rt] = lz[rt]*(r - l + 1);

        return ;

    }

    pushdown(rt,r - l + 1);

    int m = (l + r)>>1;

    if (L <= m) update(L,R,sc,l,m,rt<<1);

    if (R > m) update(L,R,sc,m + 1,r,rt<<1|1);

    pushup(rt);

}

int main()

{

    int t,x,y,z;

    int n,q;

    int cas = 1;

    scanf("%d",&t);

    while (t--)

    {

        scanf("%d",&n);

        build(1,n,1);

        scanf("%d",&q);

        while (q--)

        {

            scanf("%d%d%d",&x,&y,&z);

            update(x,y,z,1,n,1);

        }

        printf("Case %d: The total value of the hook is %d.\n",cas++,val[1]);

    }

    return 0;

}

 pku 3468 http://poj.org/problem?id=3468

题意:

给定n个数(n个数比较大,所以要用long long),有两种操作C x,y,z将区间[x,y]的数加z   Q x,y 询问区间[x,y]的总和。

思路:

典型的成端累加减;

注意:在累加减的pushdown里面的书写与成端覆盖里面的不同lz 与 val都是累加减的,因为懒惰标记的原因。

View Code
#include <cstdio>

#include <cstring>

#include <iostream>

#define maxn 100007

#define ll long long

using namespace std;



ll val[4*maxn];

int lz[4*maxn];



void pushup(int rt)

{

    val[rt] = val[rt<<1] + val[rt<<1|1];

}

void pushdown(int rt,int m)

{

    if (lz[rt])

    {

        //都要累加的。

        lz[rt<<1] += lz[rt];

        lz[rt<<1|1] += lz[rt];

        val[rt<<1] += (ll)lz[rt]*(ll)(m - (m>>1));

        val[rt<<1|1] += (ll)lz[rt]*(ll)(m>>1);

        lz[rt] = 0;

    }

}

void build(int l,int r,int rt)

{

    lz[rt] = 0;

    if (l == r)

    {

        scanf("%lld",&val[rt]);

        return ;

    }

    int m = (l + r)>>1;

    build(l,m,rt<<1);

    build(m + 1,r,rt<<1|1);

    pushup(rt);

}



void update(int L,int R,int sc,int l,int r,int rt)

{

    if (l >= L && r <= R)

    {

        lz[rt] += sc;//这里也要累加的。

        val[rt] += (ll)sc*(ll)(r - l + 1);

        return ;

    }

    pushdown(rt,r - l + 1);

    int m = (l + r)>>1;

    if (L <= m) update(L,R,sc,l,m,rt<<1);

    if (R > m) update(L,R,sc,m + 1,r,rt<<1|1);

    pushup(rt);

}



ll query(int L,int R,int l,int r,int rt)

{

    if (l >= L && r <= R)

    {

        return val[rt];

    }

    pushdown(rt,r - l + 1);

    ll res = 0;

    int m = (l + r)>>1;

    if (L <= m) res += query(L,R,l,m,rt<<1);

    if (R > m) res += query(L,R,m + 1,r,rt<<1|1);

    return res;

}



int main()

{

    int n,m;

    char op[2];

    int x,y,z;

    scanf("%d%d",&n,&m);

    build(1,n,1);

    while (m--)

    {

        scanf("%s%d%d",op,&x,&y);

        if (op[0] == 'C')

        {

            scanf("%d",&z);

            update(x,y,z,1,n,1);

        }

        else printf("%lld\n",query(x,y,1,n,1));

    }

    return 0;

}

 pku 2528 http://poj.org/problem?id=2528

题意:

给定一个高度确定,长度为10000000的墙,参加海选的人可以将自己的海报贴在上边,海报高度与墙的高度一样,给定每个海报的li,ri(表示这张海报占据li到ri这块空间),海报可以重叠,问最后将n张海报按要求张贴完毕后可看见的海报有多少张?

思路:

题目应该是按墙的长度建树,可是给定的墙的长度太大,直接搞的话会超内存,而给定的n最大取10000也就是说最多我们得到20000个点,所以价格给定的点离散化到1-2*n然后建树,接下来就是经典的成端覆盖,然后就是query访问所以叶子节点,hash统计可见海报的个数。

View Code
#include <cstdio>

#include <cstring>

#include <iostream>

#include <algorithm>

#define N 10011

using namespace std;



int hash[2*N];

int X[N*2],val[N*8];

int L[N],R[N],ans;

int cmp(int a,int b)

{

    return a < b;

}

void pushdown(int rt)

{

    if (val[rt])

    {

        val[rt<<1] = val[rt<<1|1] = val[rt];

        val[rt] = 0;

    }

}

int bsearch(int l,int r,int sc)

{

    while (l <= r)

    {

        int m = (l + r)>>1;

        if (X[m] == sc) return m;

        else if (sc < X[m]) r = m -1;

        else l = m + 1;

    }

    return l;

}



void update(int L,int R,int l,int r,int rt,int mk)

{

    if (l >= L && r <= R)

    {

        val[rt] = mk;

        return ;

    }

    pushdown(rt);

    int m = (l + r)>>1;

    if (L <= m) update(L,R,l,m,rt<<1,mk);

    if (R > m) update(L,R,m + 1,r,rt<<1|1,mk);

}



void query(int l,int r,int rt)

{

    if (l == r)

    {

        if (!hash[val[rt]])

        {

            hash[val[rt]] = 1;

            ans++;

        }

        return;

    }

    pushdown(rt);

    int m = (l + r)>>1;

    query(l,m,rt<<1);

    query(m + 1,r,rt<<1|1);

}

int main()

{

   int t,i,n;

   scanf("%d",&t);

   while (t--)

   {

       scanf("%d",&n);

       /* 这里hash[10000000]搞也可以的

       int m = 1;

       for (i = 1; i <= n; ++i)

       {

           scanf("%d%d",&L[i],&R[i]);

           if (!hash[L[i]])

           {

               hash[L[i]] = 1;

               X[m++] = L[i];

           }

           if (!hash[R[i]])

           {

               hash[R[i]] = 1;

               X[m++] = R[i];

           }

       }

       sort(X + 1,X+m,cmp);

       m--;

       */

       //不过这样时间少,内存好用也少

       int mm = 1;

       for (i = 1; i <= n; ++i)

       {

           scanf("%d%d",&L[i],&R[i]);

           X[mm++] = L[i];

           X[mm++] = R[i];

       }



       sort(X + 1,X+mm,cmp);

       int m = 2;

       for (i = 2; i < mm; ++i)

       {

           if (X[i] != X[i - 1])

           X[m++] = X[i];

       }

       m--;

       for (i = 1; i <= n; ++i)

       {

           int l = bsearch(1,m,L[i]);

           int r = bsearch(1,m,R[i]);

           update(l,r,1,m,1,i);

       }

       ans = 0;

       for (i = 0; i < 2*N; ++i) hash[i] = 0;

       query(1,m,1);

       printf("%d\n",ans);

   }

}

 

pku 3225 http://poj.org/problem?id=3225

题意:

给出一系列区间的交,并,补,差,对称差运算,最后求出得到的集合,开始集合为空;

这里主要的难点我认为有三个(这题真心不好写,整死爹了快)

1:首先是区间开与闭的处理,我们在线段树对区间操作时,左右l,r都是闭区间,这里给出的有开区间,而且又不能-1因为(2,3)这样的开区间也不为空,若-1操作无效了。这里的处理方法是所有区间的端点*2,然后处理。

例如:给你(2,3]这样的数据,如何处理呢?

我们将范围乘以2,得到(4,6],然后,如果左边是开区间,则将4加1,得到5,同理,如果右边是开区间,则将6减去一个1。数据乘以2后,得到的结果一定是偶数,而偶数加一减一后,肯定得到奇数。也就是说,如果query一遍之后最后得到的数据是偶数,那就是闭区间,如果得到的数据是奇数,那就对应着开区间。(这样处理太巧妙了YM之);

2:各个操纵对应的线段树操作了;

在数据乘以2转换成成闭区间之后对应的操作

U [a,b] 将区间[a,b]覆盖为1(成段覆盖);

I [a,b] 将区间[0,a - 1] [b +1 ,n] 覆盖成0;

D [a,b] 将区间[a,b]覆盖成0;

C [a,b] 将区间[0,a - 1] [b + 1,n]覆盖成0 区间[a,b] 0换成1 ,1换成0;(就是^1即可)

S [a,b] 将区间[a,b]  0换成1 ,1换成0;(就是^1即可)

3:就是懒惰标记以及抑或标记往下传递以及update时的操作了(我认为这里最难理解了);

lz[]为lazy标记,turn为抑或标记

lz[] == 0 表示该区间全部覆盖为0, 1表示全部覆盖为1,-1表示该区间无向下传递的操作(其左右子树可能有区间为0的区间,也可能有区间为1的区间)。

turn[] = 1 表示有抑或操作,0 表示无抑或操作

当update接受 0 ,1信息即全部覆盖为0,1时直接不用考虑原来的操作,直接覆盖就可以,而接受3抑或操作时,原来的lz 0 ,1标记直接抑或就可以。而对于lz = -1的其左右孩子可能存在整个区间为0和1的,所以要进行抑或的累积。

往下传递时首先考虑lz的传递操作,因为经过上面一些列的操作如果该区存在间既有lz操作又有turn操作时turn一定在lz之后(因为如果turn在lz之前的话,那么后来的lz直接将其覆盖了,也就无turn操作了)。

ps:我在这里处理的时候数组大小开错了,导致调了很长时间,注意数组的大小。

View Code
#include <cstdio>

#include <cstring>

#include <iostream>

#define maxn 65537

using namespace std;



int lz[8*maxn],turn[8*maxn];

bool hash[2*maxn];



void pushdown(int rt)

{

    if (lz[rt] != -1)

    {

        lz[rt<<1] = lz[rt<<1|1] = lz[rt];

        turn[rt<<1] = turn[rt<<1|1] = turn[rt];

        lz[rt] = -1;

        turn[rt] = 0;

    }

    if (turn[rt])

    {

        if (lz[rt<<1] != -1) lz[rt<<1] ^= 1;

        else turn[rt<<1] ^= 1;

        if (lz[rt<<1|1] != -1) lz[rt<<1|1] ^= 1;

        else turn[rt<<1|1] ^= 1;

        turn[rt] = 0;

    }

}

void build(int l,int r,int rt)

{

    lz[rt] = 0;

    turn[rt] = 0;

    if (l == r) return ;

    int m = (l + r)>>1;

    build(l,m,rt<<1);

    build(m + 1,r,rt<<1|1);

}



void update(int L,int R,int mk,int l,int r,int rt)

{

    if (l >= L && r <= R)

    {

        if (mk == 3)//抑或标记

        {

            if (lz[rt] != -1) lz[rt] ^= 1;//如果原来有覆盖标记

            else turn[rt] ^= 1;//如果原来没有覆盖标记

        }

        //入如果是覆盖标记直接覆盖

        else

        {

            lz[rt] = mk;

            turn[rt] = 0;

        }

        return ;

    }

    pushdown(rt);

    int m = (l + r)>>1;

    if (L <= m) update(L,R,mk,l,m,rt<<1);

    if (R > m) update(L,R,mk,m + 1,r,rt<<1|1);

}

void query(int l,int r,int rt)

{

    if (l == r)

    {

        if (lz[rt] == 1 && !hash[l])

        {

            hash[l] = true;

        }

        return ;

    }

    int m = (l + r)>>1;

    pushdown(rt);

    query(l,m,rt<<1);

    query(m + 1,r,rt<<1|1);

}

int main()

{

    //freopen("1.txt","r",stdin);

    int a,b;

    char op[2],li,ri;

    int n = 2*maxn;

    build(0,n,1);



    while (~scanf("%s %c%d,%d%c",op,&li,&a,&b,&ri))

    {

        a <<= 1; b <<= 1;

        if (li == '(') a++;

        if (ri == ')') b--;



        if (op[0] =='U') update(a,b,1,0,n,1);

        if (op[0] == 'D') update(a,b,0,0,n,1);

        if (op[0] == 'I' || op[0] == 'C')

        {

            int l = a - 1 < 0? 0:a - 1;

            int r = b + 1 > n? n:b + 1;

            update(0,l,0,0,n,1);

            update(r,n,0,0,n,1);

        }

        if (op[0] == 'C' || op[0] == 'S')

        update(a,b,3,0,n,1);

    }



    memset(hash,false,sizeof(hash));

    int s = -1,e = -1;

    bool flag = false;

    query(0,n,1);

    for (int i = 0; i <= n; ++i)

    {

        if (hash[i])

        {

            if (s == -1) s = i;

            e = i;

        }

        else

        {

            if (s != -1)

            {

                if (flag) printf(" ");

                flag = true;

                if (s&1) printf("%c%d,",'(',s/2);

                else  printf("%c%d,",'[',s/2);



                if (e&1) printf("%d%c",(e + 1)/2,')');

                else printf("%d%c",e/2,']');

                s = -1;

            }

        }

    }

   if (!flag) printf("empty set");

    puts("");

     return 0;

}

 

 

你可能感兴趣的:(线段树)