战场控制系统 线段树

NKOJ 1909 战场控制系统

问题描述

2050年,人类与外星人之间的战争已趋于白热化。就在这时,人类发明出一种超级武器,这种武器能够同时对相邻的多个目标进行攻击。凡是防御力小于或等于这种武器攻击力的外星人遭到它的攻击,就会被消灭。然而,拥有超级武器是远远不够的,人们还需要一个战地统计系统时刻反馈外星人部队的信息。这个艰巨的任务落在你的身上。请你尽快设计出这样一套系统。

这套系统需要具备能够处理如下2类信息的能力:

1.外星人向[x1,x2]内的每个位置增援一支防御力为v的部队。
2.人类使用超级武器对[x1,x2]内的所有位置进行一次攻击力为v的打击。系统需要返回在这次攻击中被消灭的外星人个数。

注:防御力为i的外星人部队由i个外星人组成,其中第j个外星人的防御力为j。

输入格式

第一行读入n,m。其中n表示有n个位置,m表示有m条信息。
以下有m行,每行有4个整数k,x1,x2,v用来描述一条信息 。k表示这条信息属于第k类。x1,x2,v为相应信息的参数。k=1 or 2。
注:你可以认为最初的所有位置都没有外星人存在。

规模

0< n<=1000;0 < x1 <= x2<=n;0 < v <= 1000;0 < m <= 2000

输出格式

按顺序输出需要返回的信息。


其实是线段树水题,但是因为对线段树的掌握还不是很好,错了很多次。

本题的规模相对其他线段树的题来说比较小,允许我们开1000个线段树维护每个防御力的外星人的分布情况。接下来的就是线段树的基本操作了。

以下为教训:

对于区间修改的题,最容易出问题的地方是lazy标记的合并。为了保证每次区间修改操作仍为O(logN)的复杂度,我们选用了打上lazy标记,在询问到时再处理的方式。如果当前节点下放lazy时,左右儿子节点也有lazy标记,那么下放时必须考虑lazy的合并。

对于lazy的合并,有绝对正确的做法:遇到左右儿子有lazy就递归处理,先处理了左右儿子的lazy下放,再处理当前节点的lazy。然而这样会使lazy退化,putdown函数失去了O(1)的时间复杂度优势我们必须尽可能保证putdown函数O(1)的时间复杂度(如:SCOI2010序列操作是线段树的模板题,但是如果没处理好各种类型lazy的合并就会WA)。

关于lazy的合并,记住以下两点:

  1. lazy的合并很多情况下不满足交换律,如本题“合并 父亲lazy=-1儿子lazy>0”和“合并 父亲lazy>0儿子lazy=-1””处理方式有所不同。
  2. 考虑lazy的合并时,除了当前节点和左右儿子,还要消去对儿子的儿子节点操作正确性造成的影响。这样其实已经有递归的意思,但是没有递归那么多的时间。

对于1,参照本题完整代码Putdown函数。

对于2,参照本题合并父亲lazy>0(整个区间加上某些数)与儿子lazy=-1(清空该区间)的情况:

对于下图,比较两段代码:
这里写图片描述

WA代码:

void Putdown(int p)
{
    int l=ls[p],r=rs[p];
    if(lazy[p]>0)
    {
        if(lazy[l]==-1)
        {
            lazy[l]=lazy[p];
            sum[l]=(b[l]-a[l]+1)*lazy[p];
        }
        else
        {
            lazy[l]+=lazy[p];
            sum[l]+=(b[l]-a[l]+1)*lazy[p];
        }
        ……
    }
    ……
}

AC代码:

void Putdown(int p)
{
    int l=ls[p],r=rs[p];
    if(lazy[p]>0)
    {
        if(lazy[l]==-1)
        {
            lazy[l]=lazy[p];
            sum[l]=(b[l]-a[l]+1)*lazy[p];
            //相比上面的代码,多了以下两行:
            lazy[ls[l]]=lazy[rs[l]]=-1;
            sum[ls[l]]=sum[rs[l]]=0;
        }
        else
        {
            lazy[l]+=lazy[p];
            sum[l]+=(b[l]-a[l]+1)*lazy[p];
        }
        ……
    }
    ……
}

可以看到,在下放1号节点的lazy值时,如果按照第一段代码,在之后下放2号节点时,lazy[3]将会被直接赋为3,而实际上,3号节点是再被清空一次之后再下放lazy=2,lazy本来应该为2。而第二段代码就很好地回避了这个问题。这就是上面说的“消去对儿子的儿子节点的影响”。

其实可以发现,多出的两行是等价于putdown(p)的,但是由于当lazy=-1时绝不会递归下去,仍然是O(1)的。在任何情况下都先下放儿子节点才会出现递归导致超时的问题。

非常改悔的完整代码:

#include<stdio.h>
#define MAXN 1005
#define MAXT 4000005
int N,M;

int rt[MAXN],ls[MAXT],rs[MAXT],tot,lazy[MAXT],a[MAXT],b[MAXT],sum[MAXT];
void Build(int p,int x,int y)
{
    a[p]=x;b[p]=y;
    if(x==y)return;
    int mid=x+y>>1;
    ls[p]=++tot;
    Build(ls[p],x,mid);
    rs[p]=++tot;
    Build(rs[p],mid+1,y);
}

void Update(int p){sum[p]=sum[ls[p]]+sum[rs[p]];}

void Putdown(int p)
{
    int l=ls[p],r=rs[p];
    if(lazy[p]==-1)
    {
        sum[l]=sum[r]=0;
        lazy[l]=lazy[r]=-1;
    }
    else
    {
        if(lazy[l]==-1)
        {
            lazy[l]=lazy[p];
            lazy[ls[l]]=lazy[rs[l]]=-1;
            sum[ls[l]]=sum[rs[l]]=0;
            sum[l]=(b[l]-a[l]+1)*lazy[p];
        }
        else
        {
            lazy[l]+=lazy[p];
            sum[l]+=(b[l]-a[l]+1)*lazy[p];
        }
        if(lazy[r]==-1)
        {
            lazy[r]=lazy[p];
            lazy[ls[r]]=lazy[rs[r]]=-1;
            sum[ls[r]]=sum[rs[r]]=0;
            sum[r]=(b[r]-a[r]+1)*lazy[p];
        }
        else
        {
            lazy[r]+=lazy[p];
            sum[r]+=(b[r]-a[r]+1)*lazy[p];
        }
    }
    lazy[p]=0;
}

void Add(int p,int x,int y)
{
    if(b[p]<x||a[p]>y)return;
    if(lazy[p])Putdown(p);
    if(b[p]<=y&&a[p]>=x)
    {
        lazy[p]++;
        sum[p]+=(b[p]-a[p]+1);
        return;
    }
    Add(ls[p],x,y);
    Add(rs[p],x,y);
    Update(p);
}

int GetSum(int p,int x,int y)
{
    if(b[p]<x||a[p]>y)return 0;
    if(lazy[p])Putdown(p);
    int L=0,R=0;
    if(b[p]<=y&&a[p]>=x)
    {
        L=sum[p];
        lazy[p]=-1;
        sum[p]=0;
        return L;
    }
    L=GetSum(ls[p],x,y);
    R=GetSum(rs[p],x,y);
    Update(p);
    return L+R;
}

int main()
{
    int i,j,k,v,x,y,Ans=0;

    scanf("%d%d",&N,&M);
    for(i=1;i<=1000;i++)rt[i]=++tot,Build(rt[i],1,N);

    for(i=1;i<=M;i++)
    {
        scanf("%d%d%d%d",&k,&x,&y,&v);
        if(k==1)for(j=1;j<=v;j++)Add(rt[j],x,y);
        else
        {
            Ans=0;
            for(j=1;j<=v;j++)Ans+=GetSum(rt[j],x,y);
            printf("%d\n",Ans);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值