线段树(lazy算法+离散化)

毕竟是写给自己看的
还是写好看一点吧

一、

最简单的

传送门 HDOJ 1754

题意~
给出N个数,两种操作:
1、U x y:修改第x个数的值为y;
2、Q x y:求第x到第y个的最大值,注:x未必比y小

标准的线段树对不对
线段树(lazy算法+离散化)_第1张图片

我们可以理解成总裁管左右两个总经理,总经理管左右两个副总经理,副总经理管左右两个经理……每个管理者都只知道自己的值,不知道他们手下的值,所以他们每次都要问自己的手下,而他们的手下每次更新的时候都要反馈给上司……这样就很好理解了

#include
#include
using namespace std;

int a[210000];//员工,只负责一开始表达自己的值

struct node //管理者(假设它是经理),管理者绝对不是员工,一定要分开来
//最底层的管理者只管一个员工,绝对不能想着:反正一个员工,就自己管自己算了。
//理解:有N个员工,就配有2N-1个管理者,用空间换时间
{
    int l,r,lc,rc,c;// 管理的员工编号是第l个到第r个(连续),lc表示左副经理的编号,rc表示右副经理是谁
    //c表示第l个员工至第r个员工的特征值:可以是和、最大值、或者最小值
}tr[410000]; int len;//tr数组就是管理者数组,len表示当前申请到第几个管理者

//到此,员工有自己的编号,管理者也有自己的编号

int mymax(int x,int y){  return x>y?x:y;} //求xy最大值的函数

void  bt(int l,int r) // build tree建立线段树,申请一个管理者,管理第l个员工至第r个员工
{
    len++; int now=len;// now记录当前管理者的编号
    tr[now].l=l; tr[now].r=r; tr[now].lc=tr[now].rc=-1;tr[now].c=0;//一开始当前管理者now的左右副总经理都是没人-1,管理者管理范围最大值为0,这里五个元素都要赋值
    if(l1人,就有权申请两个副总经理帮忙管人
    {
        int mid=(l+r)/2; // mid为l和r的中间值,从中间分为两段[l,mid]和[mid+1,r]
        tr[now].lc=len+1; bt(    l  , mid  );  // [l,mid]给左副总管,让他先去管好[l,mid]
        tr[now].rc=len+1; bt( mid+1 ,  r   );  // [mid+1,r]给右副总管,让他先去管好[mid+1,r]
    }
}

void change(int now,int x,int k)//change的功能:在当前管理者now的管理范围内,把管第x个员工的管理者(不知道该管理者的编号)的值改为k
//理解:为什么第x个一定在now的管理范围内呢?
//注意:修改,改的是管理者,不是改员工,员工已经没用了。
{
    if( tr[now].l==tr[now].r) { tr[now].c=k;return ;}//如果now只管一人,那么这个人的编号一定是x,为什么?

    int lc= tr[now].lc, rc=tr[now].rc;//找出now的左右副总分别是谁
    int mid=( tr[now].l+ tr[now].r)/2;//找到now管理范围的中间位置
    if( x<=mid)          change(lc,x,k);  //如果x在now的左副总的管理范围,那么修改这件事就交给左副总去做
    else if( mid+1<=x)   change(rc,x,k);  //如果x在now的右副总的管理范围,那么修改这件事就交给右副总去做

    tr[now].c= mymax( tr[ lc ] .c  ,  tr[ rc ].c );//修改完后,注意要维护,有可能最大值发生变化了
}

int findmax(int now,int l,int r)//findmax的功能:在当前管理者now的管理范围内,找出第l个员工至第r个员工的最大值
{
    if( l== tr[now].l &&  tr[now].r== r) return tr[now].c;//如果now的管理范围刚好是[l,r],就不用问左右副总了
    int lc= tr[now].lc, rc=tr[now].rc;
    int mid=( tr[now].l+ tr[now].r)/2;
    if( r<=mid)         return  findmax(lc,l,r); //[l,r]在左副总的管理范围内
    else if( mid+1<=l)  return  findmax(rc,l,r); //[l,r]在右副总的管理范围内
    else  return mymax(  findmax(lc,l,mid)  ,  findmax(rc,mid+1,r)  );//其他情况就是[l,r]一部分在左副总,一部分在右副总
}
int main()
{
    int n,m,i,x,y; char ss[10];
    while( scanf("%d%d",&n,&m)!=EOF)
    {
        for(i=1;i<=n;i++) scanf("%d",&a[i]);
        len=0; bt(1,n);tr[1].c=0; //初始化len为0,一开始没有一个管理者
for(i=1;i<=n;i++) change(1,i,a[i]);//初始化,把第i个位置改为a[i]
        for(i=1;i<=m;i++)
        {
            scanf("%s%d%d",ss,&x,&y);
            if(ss[0]=='Q')  printf("%d\n",  findmax(1, x,y)   );
            else change( 1, x, y);
        }
    }
    return 0;
}

虽然用线段树容易空间(时间)超限,值得庆幸的是这道题数据比较弱啊,不会超限

二、lazy算法

传送门_poj2777

题目大概是这样的:
有L段线段(编号为1~L) ,一开始 全部是颜色1。有两种操作
1、C A B tt :A~B染第tt种颜色
2、P A B :询问A~B有多少种不一样的颜色。
还是要注意A有可能比B大。

这道题每次更改的是一段数值,并且是求出一段距离的颜色数量,所以这道题体现线段树更彻底。而更新次数就比上一道题大很多了,所以我们在更新时极有可能会更新超时。所以我们这里会用到lazy算法。

所谓lazy算法就是在更新时,只更新要求更新的这一段,而不更新他的手(儿)下(子)们。当我们要访问他的这个手(儿)下(子)时,我们才去更新他的值。这样我们就能减少很多更新的次数,从而减少时间。

然而这道题我有两个不同版本的代码
先看看第一个:

#include
#include
using namespace std;

struct node
{
    int l,r,lc,rc,c;
}tr[210000]; int len;

bool v[35];

void  bt(int l,int r) // build tree
{
    len++; int now=len;
    tr[now].l=l; tr[now].r=r; tr[now].lc=tr[now].rc=-1;
    if(lint mid=(l+r)/2;
        tr[now].lc=len+1;bt(l,mid);
        tr[now].rc=len+1; bt(mid+1,r);
    }
}
void wen(int now,int l,int r)//wen(问)函数的功能:把now所管理范围中第l个员工至第r个员工的颜色都在v数组里面体现为true
{
    if( tr[now].c>0) { v[tr[now].c]=true;  return ;}// tr[now].c>0表示now管理范围的颜色是统一的,那么就不用麻烦左右副总了

    int lc= tr[now].lc, rc=tr[now].rc;
    int mid=( tr[now].l+ tr[now].r)/2;

    if( r<=mid)       wen(lc,l,r); //[l,r]在左副总的管理范围,这件事情就交给左副总去做
    else if( mid+1<=l)  wen(rc,l,r); //[l,r]在右副总的管理范围,这件事情就交给右副总去做
    else    //来到了这个else就是表示[l,r]有一部分在左副总,有一部分在右副总
    {
        wen(lc,  l    , mid );
        wen(rc, mid+1 ,  r  );
    }
}

void change(int now,int l,int r,int k)//change(改)函数的功能 :在now的管理范围内,把[l,r]改为第k种颜色
{
    if( tr[now].c==k) return ;//如果now管理的范围颜色统一,并且本来就是k,那么什么都不要做
    if( tr[now].l==l&& r==tr[now].r) { tr[now].c=k;return ;} //如果刚好now的管理范围就是[l,r],那么不管now管理范围原来是什么颜色统一改就行
    int lc= tr[now].lc, rc=tr[now].rc;
    int mid=( tr[now].l+ tr[now].r)/2;

    if(tr[now].c>0)//如果原来now的管理范围颜色统一,那么现在要改now的管理范围中的部分范围的颜色了
                   //此时now就想:我的把我原来的颜色先传给我的左右副总,因为他们还不知道他们现在的颜色(在这次改之前的颜色)
    {              //这个步骤我们称为:继承
        tr[lc].c= tr[now].c;
        tr[rc].c= tr[now].c;
    }

    if( r<=mid)         change(lc,l,r,k); //如果[l,r]在左副总的管理范围中,那么这件事情就交给左副总
    else if( mid+1<=l)   change(rc,l,r,k); //如果[l,r]在右副总的管理范围中,那么这件事情就交给右副总
    else
    {
       change(lc,  l    , mid , k );
       change(rc, mid+1 ,  r  , k );
    }
    //注意:有 修改 就配带有 维护
    if( tr[lc].c==tr[rc].c&& tr[lc].c>0  )tr[now].c= tr[ lc ] .c ;
    else tr[now].c=-1;
}
int main()
{
    int k,j,i,x,y,L,t,M,ans; char ss[10];
    while( scanf("%d%d%d",&L,&t,&M)!=EOF)
    {
        len=0; bt(1,L);tr[1].c=1;//初始化所有颜色都为1
        for(i=1;i<=M;i++)
        {
            scanf("%s",ss);
            if(ss[0]=='C')
            {
                scanf("%d%d%d",&x,&y,&k);
                if( x>y) { int tt=x;x=y;y=tt;}
                change(1,x,y,k);
            }
            else
            {
                scanf("%d%d",&x,&y); if(x>y) { int tt=x;x=y;y=tt; } //这是个坑

                memset(v,false,sizeof(v)); //一开始所有颜色都没有出现过
                wen(1,x,y);
                ans=0;  for(j=1;j<=t;j++) if( v[j]==true) ans++;
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}

相信大多数人接触的都是以上这种版本。
据说这个版本在遇到一些数据时会有bug

下面推荐另外一个版本:
通过二进制的方式来记录有多少种颜色
每次更新时只要左移c-1位就可以了
在统计时就只用查找有多少个1就可以了
那!么!
如果我们用了二进制,在更新他的上(父)司(亲)时有多少种颜色是就不能用max了
我们怎么写呢???

我们知道在最后时是统计他有多少个1
并且如果他任意一个儿子有这种颜色,即使他的另外一个儿子没有,他自己也有这种颜色
而或(|)运算正好可以满足!

推荐并提供我的代码:

#include 
#include 
using namespace std;
int len,a[100010];
struct node
{
    int lc,rc,l,r,c;bool update;
//update:false为已更新了他的儿子,true为还未更新他的儿子
//c:是颜色的状态压缩值,用二进制位表示是否选用了某种颜色
}tr[200010];
int br(int l,int r)
{
    len++;int now=len;
    tr[now].l=l;tr[now].r=r;tr[now].c=1;//与上题不一样的:由于开始整条都是颜色1,所以状态值处置设置为1,参考二进制的表示
    tr[now].update=false;
    if (lint mid=(l+r)>>1;
        tr[now].lc=br(l,mid);
        tr[now].rc=br(mid+1,r);
    }
    return now;
}
void swap(int &a,int &b)
{
    int t=a;a=b;b=t;
}
int col(int x)//统计x状态二进制位1的个数
{
    int ans=0;
    while (x>0)
    {
        ans+=x%2;
        x=x/2;
    }
    return ans;
}
void update(int x)//更新他的儿子。注意这一步一定要在访问他的儿子之前做好
{
    tr[x].update=false;
    tr[tr[x].lc].update=true;
    tr[tr[x].lc].c=tr[x].c;
    tr[tr[x].rc].update=true;
    tr[tr[x].rc].c=tr[x].c;
}
void change(int x,int l,int r,int c)
//返回值为该线段的编号
{
    if (tr[x].l==l&&tr[x].r==r) 
    {
        tr[x].c=1<<(c-1);
        //标记状态值二进制右起第c位为1,其他为0
//若整段匹配,则更新该线段update标记
        return ;
    }
    int mid=(tr[x].l+tr[x].r)>>1,lc=tr[x].lc,rc=tr[x].rc;
    if (tr[x].update) update(x);//在访问儿子之前必须更新儿子,切记!!也是该算法的重点。
    if (r<=mid) change(lc,l,r,c);
    else if (l>=mid+1) change(rc,l,r,c);
    else {change(lc,l,mid,c);change(rc,mid+1,r,c);}
    tr[x].c=(tr[lc].c|tr[rc].c);//合并左右儿子的状态。使用“或”操作计算
}
int findmax(int x,int l,int r)
{
    if (tr[x].l==l&&tr[x].r==r) return tr[x].c;
    int mid=(tr[x].l+tr[x].r)>>1,lc=tr[x].lc,rc=tr[x].rc;
    if (tr[x].update) update(x); //在访问儿子之前必须更新儿子
    if (l>=mid+1) return findmax(rc,l,r);
    else if (r<=mid) return  findmax(lc,l,r);
    else return (findmax(lc,l,mid)|findmax(rc,mid+1,r));//返回左右儿子合并值
}
int main()
{
    int n,m,t,x,y,z;
    char c;
    scanf("%d%d%d",&n,&t,&m);
    br(1,n);len=0;
    for (int i=1;i<=m;i++)
    {
        getchar();
        scanf("%c",&c);
        if (c=='C')
        {
            scanf("%d%d%d",&x,&y,&z);
            if (x>y) swap(x,y);
            change(1,x,y,z);
        }
        if (c=='P')
        {
            scanf("%d%d",&x,&y);
            if (x>y) swap(x,y);//本题坑点之一,必须注意大小关系
            printf("%d\n",col(findmax(1,x,y)));
        }
    }
    return 0;
}

上面讲了优化时间的lazy算法,下面来讲讲优化空间的

离散化

传送门_poj2528
线段树(lazy算法+离散化)_第2张图片

所谓离散化就是
原数组为A[]={3,100,9845587},
这个数组那么大,但是却只用到了很少一部分
而 {1~2,4~99,…}都没有用到
我们怎样能高效地利用这些空间呢

那么我们就要用到离散化 了
如上述的A离散化为S[]={1,2,3}

再比如:
原数组为A[]={ 3 , 100 , 9845587 , 6 , 6 , 9 , 11 , 2 },
离散化为S[]={ 2 , 6 , 7 , 3 , 3 , 4 , 5 , 1 }

离散化的过程:
1:原数组A每个数还得多带点东西:头x(原来的值),胸口p(原来的位置),肚子z(离散化的值)
2:把A复制一份为B,然后B数组根据B中x的值进行排序,然后根据B排序后的位置赋予每个B的离散值
3:让B中的每个元素带着自己的离散值和位置值去赋值A数组中的z值。A[ B[i].p ] = B[i].z ;
4:到此离散化结束,A.z就是A.x的离散值,而且一一对应。

代码:

#include
#include
#include

struct node{int x,y,p;};//离散化用(意思同上述)
node a[20010],b[20010];
int n,mm;
struct tree{int l,r,lc,rc,c;bool update;
//update是否有更新儿子(lazy),真需要更新假已更新};
tree tr[40010];
int len=0;
bool col[20010];

void qsort(int l,int r)//快排
{
    int i=l,j=r;
    node mid,t;
    mid=b[(l+r)>>1];
    while (i<=j)
    {
        while (b[i].xx) i++;
        while (b[j].x>mid.x) j--;
        if (i<=j)
        {
            t=b[i];b[i]=b[j];b[j]=t;
            i++;j--;
        }
    }
    if (lif (iint bt(int l,int r)
{
    len++;
    int now=len;
    tr[now].l=l;
    tr[now].r=r;
    tr[now].c=0;
    tr[now].lc=-1;
    tr[now].rc=-1;
    tr[now].update=false;
    if (l+1!=r)
    {
        int mid;
        mid=(l+r)>>1;
        tr[now].lc=bt(l,mid);
        tr[now].rc=bt(mid,r);
    }
    return now;
}

void update(int x)//lazy算法
{
    tr[x].update=false;
    if (tr[x].lc!=-1 && tr[x].rc!=-1) 
    {
        tr[tr[x].lc].update=tr[tr[x].rc].update=true;
        tr[tr[x].lc].c=tr[tr[x].rc].c=tr[x].c;
    }
}

void insert(int x,int l,int r,int p)//change
{
    if (tr[x].l==l && tr[x].r==r)
    {
        tr[x].c=p;
        tr[x].update=true;
        return;
    }
    if (tr[x].update) update(x);
    int mid;
    mid=(tr[x].l+tr[x].r)>>1;
    if (r<=mid) insert(tr[x].lc,l,r,p);
    else if (l>=mid) insert(tr[x].rc,l,r,p);
    else {insert(tr[x].lc,l,mid,p);insert(tr[x].rc,mid,r,p);};
}

int main()
{
    int m;
    scanf("%d",&m);
    for (int u=1;u<=m;u++)
    {
        scanf("%d",&n);n=n<<1;
        for (int i=1;i<=n;i+=2)
        {
            scanf("%d %d",&a[i].x,&a[i+1].x);
            a[i+1].x++;
            a[i].p=i;
            a[i+1].p=i+1;
            //记录
        }
        for (int i=1;i<=n;i++)
            b[i]=a[i];
        qsort(1,n);
        b[1].y=1;
        for (int i=2;i<=n;i++)
            if (b[i].x==b[i-1].x) b[i].y=b[i-1].y;
            else b[i].y=b[i-1].y+1;//离散化
        for (int i=1;i<=n;i++)
        {
            a[b[i].p].y=b[i].y;//记录位置
        }
        len=0;
        bt(1,b[n].y);
        for (int i=1;i<=n;i+=2)
        {
            insert(1,a[i].y,a[i+1].y,i);
        }
        memset(col,false,sizeof(col));
        for (int i=1;i<=len;i++)
        {
            if (tr[i].update) update(i);
            if (tr[i].lc==-1) col[tr[i].c]=true;//统计
        }
        int ans=0;
        for (int i=1;i<=n;i++)
            if (col[i]) ans++;
        printf("%d\n",ans);
    }
}

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