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的合并,记住以下两点:
- lazy的合并很多情况下不满足交换律,如本题“合并 父亲lazy=-1与儿子lazy>0”和“合并 父亲lazy>0与儿子lazy=-1””处理方式有所不同。
- 考虑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);
}
}
}