线段树复习

Posted 日拱一卒 功不唐捐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树复习相关的知识,希望对你有一定的参考价值。

2017.3.24

T1   最大子段和 http://codevs.cn/problem/3981/

初做:2017.2.1  time:2576ms   memory:22MB

http://www.cnblogs.com/TheRoadToTheGold/p/6360224.html

现在:2017.3.24  time:2991ms  memory:29MB

#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 200001
using namespace std;
int n,m,tot;
struct node
{
    int l,r,siz;
    long long lmax,rmax,maxx,sum;
}tr[N*2];
void up(int k)
{
    int l=k+1,r=k+tr[k+1].siz*2;
    tr[k].lmax=max(tr[l].lmax,tr[l].sum+tr[r].lmax);
    tr[k].rmax=max(tr[r].rmax,tr[l].rmax+tr[r].sum);
    tr[k].sum=tr[l].sum+tr[r].sum;
    long long tmp1=max(tr[l].maxx,tr[r].maxx);
    long long tmp2=max(tr[k].lmax,tr[k].rmax);
    long long tmp3=max(tmp1,tmp2);
    tr[k].maxx=max(tmp3,tr[l].rmax+tr[r].lmax);
}
void build(int l,int r)
{
    tr[++tot].l=l;tr[tot].r=r;
    tr[tot].siz=r-l+1;
    int k=tot;
    if(l==r)
    {
        cin>>tr[tot].maxx;
        tr[tot].lmax=tr[tot].rmax=tr[tot].sum=tr[tot].maxx;
        return;
    }
    int mid=l+r>>1;
    build(l,mid);build(mid+1,r);
    up(k);
}
void query(int k,int opl,int opr,long long & ans_l,long long &ans_r,long long &ans_sum,long long &ans)
{
    if(tr[k].l==opl&&tr[k].r==opr)
    {
        ans_l=tr[k].lmax;
        ans_r=tr[k].rmax;
        ans_sum=tr[k].sum;
        ans=tr[k].maxx;
        return;
    }
    int mid=tr[k].l+tr[k].r>>1,l=k+1,r=k+tr[k+1].siz*2;
    if(opr<=mid) query(l,opl,opr,ans_l,ans_r,ans_sum,ans);
    else if(opl>mid) query(r,opl,opr,ans_l,ans_r,ans_sum,ans);
    else
    {
        long long lch_lmax,lch_rmax,lch_sum,lch_maxx;
        long long rch_lmax,rch_rmax,rch_sum,rch_maxx;
        query(l,opl,mid,lch_lmax,lch_rmax,lch_sum,lch_maxx);
        query(r,mid+1,opr,rch_lmax,rch_rmax,rch_sum,rch_maxx);
        ans_l=max(lch_lmax,lch_sum+rch_lmax);
        ans_r=max(rch_rmax,rch_sum+lch_rmax);
        ans_sum=lch_sum+rch_sum;
        long long tmp1=max(lch_maxx,rch_maxx);
        long long tmp2=max(ans_l,ans_r);
        long long tmp3=max(tmp1,tmp2);
        ans=max(tmp3,lch_rmax+rch_lmax);
    }
}
int main()
{
        freopen("data","r",stdin);
    freopen("2.out","w",stdout);
    scanf("%d",&n);
    build(1,n);
    scanf("%d",&m);
    int x,y;
    long long ans_l,ans_r,ans_sum,ans;
    while(m--)
    {
        scanf("%d%d",&x,&y);
        query(1,x,y,ans_l,ans_r,ans_sum,ans);
        printf("%lld\\n",ans);
    }
}
View Code

画蛇添足:

tmp1=max(lch_max,rch_max)

tmp2=max(l_max,r_max)

tmp3=max(tmp1,tmp2)

ans=max(tmp3,l_rmax+r_lmax)

优化:ans=max(tmp1,l_rmax+r_lmax)

原因:如果全部的左子区间+右子区间左半部分最优,那么左子区间的右半部分=全部的左子区间

加深理解:

ans_l=max(lch_lmax,lch_sum+rch_lmax);
ans_r=max(rch_rmax,rch_sum+lch_rmax);

ans_l != tr[l].lmax

ans_r != tr[r].rmax

ans_l是自下一层开始递归,直至找到符合要求的tr[].lmax

这个符合要求的tr[].lmax不一定就是当前左子区间的lmax

疑问:

既然如此,那lch_sum也应该是符合要求的tr[].sum,而不一定当前左子区间的sum

但如若直接用tr[l].sum代替lch_sum也能AC,且对拍无误

why?

 

T2  https://www.luogu.org/problem/show?pid=2894

只有0和1,找到连续0的个数超过x的位置,输出最左端,支持区间修改操作

初做:2017.2.1  time:354ms   memory:68.18MB

多开了10倍的结构体,重测:21.24MB

http://www.cnblogs.com/TheRoadToTheGold/p/6360248.html

现在:2017.3.24  time:1030ms  memory:18.63MB

#include<cstdio>
#include<algorithm>
#define N 50001
using namespace std;
int n,m,tot,ans;
struct node
{
    int l,r,lmax,rmax,maxx,siz;
    int f;
}tr[N*2];
void build(int l,int r)
{
    int k=++tot;
    tr[k].l=l;tr[k].r=r;
    tr[k].lmax=tr[k].rmax=tr[k].maxx=tr[k].siz=r-l+1;
    if(l==r) return;
    int mid=l+r>>1;
    build(l,mid);build(mid+1,r);
}
void down(int k)
{
    int l=k+1,r=k+tr[k+1].siz*2;
    if(tr[k].f==1)
    {
        tr[l].lmax=tr[l].rmax=tr[l].maxx=0;
        tr[r].lmax=tr[r].rmax=tr[r].maxx=0;
    }
    else
    {
        tr[l].lmax=tr[l].rmax=tr[l].maxx=tr[l].siz;
        tr[r].lmax=tr[r].rmax=tr[r].maxx=tr[r].siz;
    }
    tr[l].f=tr[r].f=tr[k].f;
    tr[k].f=0;
}
int query(int k,int l,int x)
{
    if(tr[k].lmax>=x) return l;
    if(tr[k].f) down(k);
    int mid=tr[k].l+tr[k].r>>1,lc=k+1,rc=k+tr[k+1].siz*2;
    if(tr[lc].maxx>=x)     return query(lc,l,x);
    if(tr[lc].rmax+tr[rc].lmax>=x) return mid-tr[lc].rmax+1;
    return query(rc,mid+1,x);
}
void up(int k)
{
    int l=k+1,r=k+tr[k+1].siz*2;
    if(tr[l].lmax==tr[l].siz) tr[k].lmax=tr[l].siz+tr[r].lmax;
    else tr[k].lmax=tr[l].lmax;
    if(tr[r].rmax==tr[r].siz) tr[k].rmax=tr[l].rmax+tr[r].siz;
    else tr[k].rmax=tr[r].rmax;
    tr[k].maxx=max(max(tr[l].maxx,tr[r].maxx),tr[l].rmax+tr[r].lmax);
}
void change(int k,int opl,int opr,int w)
{
    if(tr[k].l>=opl&&tr[k].r<=opr)
    {
        if(w==1) tr[k].lmax=tr[k].rmax=tr[k].maxx=0;
        else tr[k].lmax=tr[k].rmax=tr[k].maxx=tr[k].siz;
        tr[k].f=w;
        return;
    }
    if(tr[k].f) down(k);
    int mid=tr[k].l+tr[k].r>>1,l=k+1,r=k+tr[k+1].siz*2;
    if(opl<=mid) change(l,opl,opr,w);
    if(opr>mid) change(r,opl,opr,w);
    up(k);
}
int main()
{
    scanf("%d%d",&n,&m);
    build(1,n);
    int x,y,z;
    while(m--)
    {
        scanf("%d",&z);
        if(z==1)
        {
            scanf("%d",&x);
            if(tr[1].maxx<x) 
            {
                printf("0\\n");
                continue;
            }
            ans=query(1,1,x);
            printf("%d\\n",ans);
            change(1,ans,ans+x-1,1);
        }
        else
        {
            scanf("%d%d",&x,&y);
            change(1,x,x+y-1,2);
        }
    }
}
View Code

3个错误:

① 父节点编号k,左子节点为k+1,右子节点为k+tr[k+1].siz*2

没有+1,因为左子树节点数为2*tr[k+1].siz-1

② 区间修改为1时,实际操作区间opl,opr与当前递归区间l,r混淆

既然记录了siz信息,为啥不直接用呢

③ query时忘了下传标记

思路卡壳处:

 要求输出最左端,而线段树里没有记录这一信息。

可以在调用函数时设一实参表示,lmax满足要求时直接返回这一参数,这一点后来想到了

因为输出最左端,所以跨左右区间的优于在右子区间的

这一点在没过样例之后想到了,但如何处理

一开始的想法是递归左子区间,找>=x-tr[r].lmax的位置

真是脑抽了,明显不对

没忍住看了以前做的,是直接返回mid-tr[l].rmax+1

因为如果跨区间,那么最左端位置就固定了

以前写的跟这次的不大一样,仔细想想,他主要集中在了这一句

if(e[(k<<1)+1].max_l+e[k<<1].max_r>=x) return mid-e[k<<1].max_r+1;

(以前的代码)

 

以上是关于线段树复习的主要内容,如果未能解决你的问题,请参考以下文章

线段树复习打卡——1318: 借教室

线段树简单操作模板复习(忘了。)

线段树详解

[HDOJ3308]LCIS(线段树,区间合并,新的代码)

数据结构复习1

冲刺noibanzi复习计划