HDU1540(线段树查找连续子区间+单点更新)

啊,经过20多次OJ的提交判断,耗费了我很多心血,终于找出来了隐藏在代码中的一个小错误。

AC程序如下所示: 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=50050;
struct node
{
    //ls表示从这个区间左端开始的最大连续区间
    //rs表示这个区间右端开始的最大连续区间
    //ms表示这个区间的最大连续区间
    int l,r;
    int ls,rs,ms;
} tree[maxn*3];
void build(int l,int r,int rt)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].ls=tree[rt].ms=tree[rt].rs=r-l+1;
    if(l==r)
        return;
    int m=(l+r)>>1;
    build(l,m,rt<<1);
    build(m+1,r,(rt<<1)|1);
}
void pushup(int rt)
{
    tree[rt].ls=tree[rt<<1].ls;
    tree[rt].rs=tree[(rt<<1)|1].rs;
    if(tree[rt].ls==tree[rt<<1].r-tree[rt<<1].l+1)
        tree[rt].ls+=tree[(rt<<1)|1].ls;
    if(tree[rt].rs==tree[(rt<<1)|1].r-tree[(rt<<1)|1].l+1)
        tree[rt].rs+=tree[rt<<1].rs;
    tree[rt].ms=max(tree[rt<<1].ms,tree[(rt<<1)|1].ms);
    tree[rt].ms=max(tree[rt].ms,tree[rt<<1].rs+tree[(rt<<1)|1].ls);
}
void update(int rt,int flag,int L)
{
    if(tree[rt].l==tree[rt].r)
    {
        if(flag==1)
            tree[rt].ls=tree[rt].ms=tree[rt].rs=1;
        else
            tree[rt].ls=tree[rt].ms=tree[rt].rs=0;
        return;
    }
    int m=(tree[rt].l+tree[rt].r)>>1;
    if(L<=m)
        update(rt<<1,flag,L);
    else
        update((rt<<1)|1,flag,L);
    pushup(rt);
}
int query(int rt,int t)
{
    //L是目标值
    if(tree[rt].l==tree[rt].r||tree[rt].ms==0||tree[rt].ms==tree[rt].r-tree[rt].l+1)
        return tree[rt].ms;
    int m=(tree[rt].l+tree[rt].r)>>1;
    if(t<=m)
    {
        if(t>=tree[rt<<1].r-tree[rt<<1].rs+1)
            return query(rt<<1,t)+query((rt<<1)|1,m+1);
        else
            return query(rt<<1,t);
    }
    else
    {
        if(t<=tree[(rt<<1)|1].l+tree[(rt<<1)|1].ls-1)
            return query((rt<<1)|1,t)+query(rt<<1,m);
        else
            return query((rt<<1)|1,t);
    }
}
char str[10];
int stak[maxn];//模拟栈

int main()
{
    int m,top,L;
    int n;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        build(1,n,1);
        top=0;
        for(int i=0; i<m; ++i)
        {
            scanf("%s",&str);///这可能浪费时间
            if(str[0]=='D')
            {
                scanf("%d",&L);
                stak[top++]=L;
                update(1,0,L);
            }
            else if(str[0]=='Q')
            {
                scanf("%d",&L);
                printf("%d\n",query(1,L));
            }
            else
            {
                if(L>0)
                {
                    L=stak[--top];
                    update(1,1,L);
                }
            }
        }
    }
    return 0;
}

1.错误代码段是:

int query(int rt,int t)
{
    //L是目标值
    if(tree[rt].l==tree[rt].r||tree[rt].ms==0||tree[rt].ms==tree[rt].r-tree[rt].l+1)
        return tree[rt].ms;

错误原因是:将第三个判定条件r和l颠倒啦!!!

血的教训:在线段树中涉及到区间两端点值的减法运算时,一定记住:r比l大!!!

2.下面需要解决的问题是程序的原理问题:

上述在结构体中定义成员变量之前有三行注释。这三行注释对于我理解程序起的作用非常大!有了这三行注释,程序的逻辑思维变得清晰明了,但是对于解决问题的正确性的证明却不简单。

3.程序原理正确性的证明如下:

【1】初始化线段树使用将叶节点赋值为1的方法。这种情况可以直接计算出线段树每个位置的区间总值。包括ls,ms,rs三个元素的值。

【2】单点更新包括D和R操作。使用线段树单点更新的方法,D操作将目标叶节点赋值为0(包括叶节点ls,ms,rs三个元素)。R操作赋值为1。最重要的是更新操作:涉及到本节点ls,ms,rs三个成员变量的更新。

首先:本节点ls值被左子树ls赋值可以理解。同理本节点rs值被右子树rs值赋值。同时考虑(一旦ls==tree[rt<<1].r-tree[rt<<1].l+1)说明左子树已经完全连续(完全连续即:没有毁掉的村庄,没有断点)此时应当使tree[rt].ls+=tree[rt<<1|1].ls可以理解。同理可得右子树完全连续的情况结论。

其次:本节点的ms值应当是下列三种来源中最大值:左子树ms,右子树ms和整个区间中穿过中间点的连续区间(tree[rt<<1].rs+tree[rt<<1|1].ls)。

【3】查询操作的正确性证明:

查询操作很重要也非常有分析的必要。明确:查询函数返回的包括目标结点在内的连续区间的最大值。

因而首先:递归结束的首要条件不难理解:如果当前区间的ms值为特殊值(满或者空)或者当前查询点为特殊点(叶节点),返回ms值。

其次:不是特殊值的情况下需要考虑当前区间的查询值和左右子树查询值的关系。

第一步:判断目标结点在左子树还是在右子树。判断完成后需要考虑特殊情况:目标结点在子树独立的连续区间还是在子树和另一个子树连接的连续区间内。所谓独立连续区间的定义是:这个连续区间是否连接区间中间值m,如果连接中间值则不是独立的连续区间,否则是独立的连续区间。

在这里,笔者想到了一个问题:应不应该考虑和本区间(包括了左子树和右子树)连接的左边和右边的区间呢?

我的解释是:查询函数反应的是查询线段树rt结点时和目标结点有关系的相关值,因此以rt区间为定义域。

第二步:分情况讨论:

如果目标结点在子树的独立连续区间内,返回以该子树区间为定义域以目标结点为目标值的查询值。

如果在和另一个子树连接的连续区间内,除了上述再加上以另一个子树区间为定义域以中间值m为目标值的查询值。

至此,程序原理的正确性说明完毕(虽然只是解释了一下源程序,但是总算理清了思路)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值