线段树练习——成段更新

这一块关键理解延迟标记(或者说懒惰标记)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;
}

 

 

posted @ 2012-07-24 14:38  E_star  阅读(329)  评论(0编辑  收藏  举报