<<  < 2013 - >  >>
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31




如果说以前写的树状数组都不是正宗的线段数的话,那么昨天晚上写的绝对是正宗的线段数了。

昨晚从约19.00开始一直写到00.00,累死了  最终还是被我调出来了。。。

其实回头看昨天写的题目,才真正理解线段树的优越性所在。优越的关键在于一个叫做lazy标记数组的神奇的东西(就是一个普通的数组啦哈)

但是用这个lazy数组做标记  , 意义在于可以用来记录以改节点为父节点的子节点是否被更新过。

但需要修改或查询这个区间时,如果发现没有更新过的话,那么就将lazy数组下放,进而实现更新。同时下放后,有的区间也是不需要更新的,这样又可以节省出许多的时间来。

第一个题是《POJ 4047 Garden》题目的大意就是按顺序给出若干数字,对于若干条命令,有的命令是修改数组中某个数的值,有的则是要求出一个区间中,连续K个数之和的最大值。

这是一个压缩数值后的线段数。主要是修改这一部分。根据修改数值的下标,可以推算出该数的改变可能影响到的压缩后的区间的范围,然后才能继续进行updata函数的操作。代码如下:

#i nclude <iostream>
#i nclude <cstdio>
#define maxn 250010
using namespace std;
int a[maxn],b[maxn],sum[4*maxn],col[4*maxn];
int n,m,k,END;

void build(int rt, int l, int r)
{
    col[rt]=0; sum[rt]=0;
    if (l==r)
    {
        sum[rt]=b[l+k-1]-b[l-1];
        return;
    }
    int mid=(l+r)/2;
    build(rt*2,l,mid);
    build(rt*2+1,mid+1,r);
    sum[rt]=max(sum[2*rt],sum[2*rt+1]);
}

void PushDown(int rt)
{
    if (col[rt]==0) return;
    col[2*rt]+=col[rt];
    col[2*rt+1]+=col[rt];
    sum[2*rt]+=col[rt];
    sum[2*rt+1]+=col[rt];
    col[rt]=0;
}

void updata(int L,int R,int c,int l,int r,int rt)
{
    if (L<=l && R>=r)
    {
        sum[rt]+=c,col[rt]+=c;return;
    }
    PushDown(rt);
    int mid=(l+r)/2;
    if (L<=mid) updata(L,R,c,l,mid,2*rt);
    if (R>mid) updata(L,R,c,mid+1,r,2*rt+1);
    sum[rt]=max(sum[2*rt],sum[2*rt+1]);
}

void work(int v, int c)
{
    int L=max(1,v-k+1),R=min(END,v);
    updata(L,R,c,1,END,1);
}

int query(int rt,int l,int r,int L,int R)//大写的为要查询的总区间,小写为当前树的端点
{
    if (L<=l && R>=r) return sum[rt];
    int mid=(l+r)/2,ans=-999999999;
    PushDown(rt);
    if (L<=mid) ans=query(rt*2,l,mid,L,R);
    if (R>mid) ans=max(ans,query(2*rt+1,mid+1,r,L,R));
    return ans;
}

int main()
{
    int t,p,x,y;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d %d %d",&n,&m,&k);
        END=n-k+1;  b[0]=0;
        for (int i=1; i<=n; i++) scanf("%d",&a[i]),b[i]=b[i-1]+a[i];
        build(1,1,END);
        while (m--)
        {
            scanf("%d%d%d",&p,&x,&y);
            if (p==2)
            {
                printf("%d\n",query(1,1,END,x,y-k+1));
            }
            else if (p==0)
            {
                work(x,y-a[x]),a[x]=y;
            }
            else if (p==1)
            {
                work(x,a[y]-a[x]);
                work(y,a[x]-a[y]);
                swap(a[x],a[y]);
            }
        }
    }
    return 0;
}
比较难以理解的可能是work函数,其具体作用就是推算影响到的区间,继而更新。
 
然后今天做的这个线段树的题目要稍稍简单一点点。这个题目是《POJ 2777 Count Color》
大意是给定一个长度为L的板,在每个单位长度上可以覆盖一种颜色。中途可以对任何一个区间上的颜色进行修改。对于每个输入的'P',要求输出给定区间上的颜色的种类数。
但是题目还有一个小小的条件引起了我的注意,T(颜色种类数)<=30,邪恶了。(就是2^T<max int)
显然,可以用位运算来记录区间上的颜色种类,这样就显得方便灵活多了。
同时,由于题目有说明,在开始的时候,所以的单位长度的板上都被涂上了颜色1,显然这样的话build建树这个函数可以省略了,只要在标记数组上写个col[1]=1;就哦科啦,因为标记数组会一直往下面更新,这样有的地方不用的就不会被更新到,显然又是一个节省时间的好方法了。。。
本题比较简单,不多说了。具体代码如下:
#i nclude <iostream>
#i nclude <cstdio>
#define maxn 111111
using namespace std;
int sum[3*maxn],col[3*maxn];
int L,T,O;

int countnum(int x)
{
    int ans=0;
    while (x>0)
    {
        if (x%2==1) ans++;
        x/=2;
    }
    return ans;
}

void PushDown(int rt)
{
    if (col[rt]==0) return;
    col[2*rt]=col[rt];
    col[2*rt+1]=col[rt];
    sum[2*rt]=col[rt];
    sum[2*rt+1]=col[rt];
    col[rt]=0;
}

void ***(int rt,int l,int r,int left,int right,int color)
{
    if (left<=l && right>=r)
    {
        col[rt]=1<<(color-1);
        sum[rt]=1<<(color-1);
        return;
    }
    PushDown(rt);
    int mid=(l+r)/2;
    if (left<=mid) ***(2*rt,l,mid,left,right,color);
    if (right>mid) ***(2*rt+1,mid+1,r,left,right,color);
    sum[rt]=sum[2*rt] | sum[2*rt+1] ;
}

int query(int rt,int l,int r,int left,int right)
{
    if (left<=l && right>=r) return sum[rt];
    PushDown(rt);
    int mid=(l+r)/2,ans=0;
    if (left<=mid) ans=query(2*rt,l,mid,left,right);
    if (right>mid) ans=ans|query(2*rt+1,mid+1,r,left,right);
    return ans;
}

int main()
{
    char s[3];
    int A,B,C;
    scanf("%d%d%d",&L,&T,&O);
    col[1]=1;
    while (O--)
    {
        scanf("%s",s);
        if (s[0]=='P')
        {
            scanf("%d%d",&A,&B);
            printf("%d\n",countnum(query(1,1,L,min(A,B),max(A,B))));
        }
        else if (s[0]=='C')
        {
            scanf("%d%d%d",&A,&B,&C);
            ***(1,1,L,min(A,B),max(A,B),C);
        }
    }
    return 0;
}
 
感觉昨天晚上和今天进步很大,继续加油!   ^_^
发表评论:
天涯博客欢迎您!