您的位置:首页 > 其它

hdu 3954 线段树 Level up 很有特色的一个题(关于lazy操作)

2012-08-21 10:55 531 查看
这题是个线段树。

题意:成段更新,成段询问,但是更新却和一般的线段树不大一样,每个点虽然接收到相同的信息,但是由于本身不同,最终得到的值也是不同的.用一般的延迟操作就搞不定了,我觉得这是这个题目最大的特色所在,做完之后的感觉很有趣。

解:

TLE解法:用level表示子区间的level和,用Exp表示子区间最大Exp。区间更新的时候,如果这个子区间都达到了max_level的话,我们就不用去更改它的子节点的level值,只是将要乘以的ei记录在val里面就行。push_dn的时候要是这个区间都是levelk,就将这个记录的值给push下去。

正解1: 一棵线段树。

这题不像平常的lazy操作,你就一直存着那个等级系数ei(之所以叫它等级系数是因为这个: ei * 等级),到要查子区间的时候就push就行了。不能一直存着的原因是因为ei会使等级增加以致后面来的ei产生的经验值随着等级的增加而增加得更快。

为了解决此等困境,我们用一个mn[]表示这个区间升级需要多少的等级系数,已超过这个,我们就释放。这样,也就lazy了。lazy存的是可以累加的等级系数。若超过mn了,意味着不能再继续累加了。于是,我们就更新到底了。。。

关于lazy的操作,我将以前简单的lazy理解了理解,这里的lazy就理解了。需要注意的是,每个节点的每个东西都写成那种已经释放lazy的,这样push_dn的时候不用对本身操作,更新了子区间也好更新父亲区间。而且,这样更好理解。

正解2:十棵线段树(出题人题解)

用了两个辅助数组add[maxn]和MAX[maxk][maxn],add用于记录延迟标记,MAX[k]表示该区间等级为k的最大经验值.初始化add,MAX[1]为0,其他为-1,表示无人在这个等级.当MAX[k]的值大于等于Needk时,就对这个区间进行升级操作,和线段树操作一样递归的将这个区间能升级的人全部升级.

单次操作可能会是nlogn(每个人都升级),但是平均下来还是只有nklogn.

两种解都是运用了这个:

突破点在K,范围很小,只有10,可以考虑每次有人升级的时候,就递归的找下去,将这个人进行升级操作.由于找到某个人只需要logn的复杂度,每个人最多升k次,所以n个人的复杂度是O(nklogn)

做题过程:

写了一遍,没改什么就出样例了。甚是高兴。就屁颠屁颠地交了。T了。虽然知道这题卡时间,但是没料到会T。果断改中。

后来用的第一种解法过的。

再后来用了第二种解法过的。

/*
Pro: 0

Sol:

date:
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <vector>
#define maxn 10020
#define lson l,m,rt << 1
#define rson m + 1, r, rt << 1 | 1
#define ls  rt << 1
#define rs rt << 1 | 1
#define havem int m = (l + r ) >> 1
using namespace std;
int li,ri,t,needk[11],n,k,Q,exp[maxn << 2],level[maxn << 2],lazy[maxn << 2], mn[maxn << 2], ei;
char op[10];
void push_up(int rt){//这里没有lazy什么事情,lazy只往下,不往上
mn[rt] = min(mn[ls],mn[rs]);
level[rt] = max(level[ls],level[rs]);
exp[rt] = max(exp[ls],exp[rs]);
}
void build(int l, int r, int rt){
lazy[rt] = 0;
if(l == r){
level[rt] = 1;//开始的level为1
exp[rt] = 0;//经验值为0
mn[rt] = needk[2] / 1;//最小升值经验系数为比自身高一级所需经验/自身所在等级
return ;
}havem;
build(lson);    build(rson);
push_up(rt);//刚开始写的时候,居然忘记push_up了,这样的话就初始化地不对了。
}
void push_dn(int rt){
if(lazy[rt]){
lazy[ls] += lazy[rt];
lazy[rs] += lazy[rt];
/*********************************/
exp[ls] += level[ls] * lazy[rt];
exp[rs] += level[rs] * lazy[rt];
/*********************************/
mn[ls] -= lazy[rt];
mn[rs] -= lazy[rt];
lazy[rt] = 0;
}
}
//lazy 里面存的是经验系数
void update(int& L, int& R, int& cc, int l, int r, int rt){
if(l == r){
exp[rt] += level[rt] * cc;//?
while(exp[rt] >= needk[level[rt] + 1]) level[rt] ++;
mn[rt] = (needk[level[rt] + 1] - exp[rt]) / level[rt] +
((needk[level[rt] + 1] - exp[rt]) % level[rt] != 0);
return ;
}
if(L <= l && r <= R){
if(cc < mn[rt]){//没有达到这个门槛
mn[rt] -= cc;
lazy[rt] += cc;
exp[rt] += level[rt] * lazy[rt];
return;
}
}push_dn(rt);   havem;
if(L <= m) update(L,R,cc,lson);
if(R > m) update(L,R,cc,rson);
push_up(rt);
}
int query(int& L,int& R, int l, int r, int rt){
if(L <= l && r <= R)    return exp[rt];
push_dn(rt);    havem;

if(R <= m)  return query(L,R,lson);
else if(L > m) return query(L,R,rson) ;
return max(query(L,R,lson),query(L,R,rson));
}
int main(){
scanf("%d",&t);
for(int ca = 1; ca <= t; ca ++){
printf("Case %d:\n",ca);
scanf("%d%d%d",&n,&k,&Q);
for(int i = 2; i <= k; i ++)
scanf("%d",&needk[i]);
needk[k + 1] = 1 << 30;//这个很重要,或者可以在上面循环的时候加个判断,让等级不超过k - 1级
build(1,n,1);
for(int i = 1; i <= Q; i ++){
scanf("%s",op);
if(op[0] == 'W'){
scanf("%d%d%d",&li,&ri,&ei);
update(li,ri,ei,1,n,1);
}else{
scanf("%d%d",&li,&ri);
printf("%d\n",query(li,ri,1,n,1));
}
}
puts("");//PE了一次,哎,格式还是很重要的。
}
return 0;
}
十棵线段树:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define lson l, m , rt << 1
#define rson m + 1, r, rt << 1 | 1
#define ls rt << 1
#define rs rt << 1 | 1
#define havem int m = (l + r) >> 1
#define maxn 100100
const int maxk = 12;
using namespace std;
int Max[maxk][maxn << 2],add[maxn << 2],n,k,needk[maxk],t,q;
void build(int l, int r, int rt){
Max[1][rt] = add[rt] = 0;//等级为1的最大经验值初始化为0,lazy操作也为0
for(int i = 2; i <= k; i ++)
Max[i][rt] = -1;//为-1表示这个区间没有等级为i的。
if(l == r) return ;
havem;
build(lson);    build(rson);
}
void push_up(int rt){//?
for(int i = 1; i <= k; i ++)
Max[i][rt] = max(Max[i][ls],Max[i][rs]);
}
void push_dn(int rt){
if(add[rt]){
add[ls] += add[rt];
add[rs] += add[rt];
for(int i = 1; i <= k; i ++){
if(Max[i][ls] != -1) Max[i][ls] += add[rt] * i;
if(Max[i][rs] != -1) Max[i][rs] += add[rt] * i;
}
add[rt] = 0;
}
}
void level_up(int i, int l , int r, int rt){//i 是需要升级的等级
if(l == r){
while(i < k){
if(Max[i][rt] < needk[i]) break;
Max[i + 1] [rt] = Max[i][rt];
Max[i][rt] = -1;//表示没有这个等级的了
i ++;
} return ;
}push_dn(rt);  havem;
if(Max[i][ls] >= needk[i]) level_up(i,lson);
if(Max[i][rs] >= needk[i]) level_up(i,rson);
push_up(rt);
}
//这个不同的是把每个等级在这个区间当中的最大经验值都记录下来了
//这样,一旦这个最大值大于升级需要的等级,就让他升个级
void update(int L,int R,int cc,int l, int r, int rt){
if(L <= l && r <= R){
add[rt] += cc; //每次先释放了
for(int i = k; i >= 1; i --){//这里需要注意,为什么一定要从k for 到1呢
//是因为这样得到的才是原始值。
//从1到k的更新后,i级升到i+1级,此时,i+1级已经加过cc了,这样会重复
if(Max[i][rt] != -1) Max[i][rt] += cc * i;//这个时候乘的还是i,没有关系,!= -1表示这个区间当中有等级为i的
if(i < k && Max[i][rt] >= needk[i]){//小于k才能升级
level_up(i,l,r,rt);//升级必然升级到底
}
}
return ;
}
push_dn(rt);    havem;
if(L <= m) update(L,R,cc,lson);
if(R > m) update(L,R,cc,rson);
push_up(rt);
}
int query(int L,int R,int l,int r, int rt){
if(L <= l && r <= R){
for(int i = k; i >= 1; i --)//减减啊。。。亲
if(Max[i][rt] != -1) return Max[i][rt];//
}push_dn(rt);   havem;
int ans = 0;
if(L <= m) ans = query(L,R,lson);
if(R > m) ans = max(ans, query(L,R,rson));
return ans;
}
int main(){
scanf("%d",&t);
for(int ca = 1; ca <=t ; ca ++){
printf("Case %d:\n",ca);
scanf("%d%d%d",&n,&k,&q);
build(1,n,1);
for(int i = 1; i < k; i ++) scanf("%d",needk + i);
while(q --){
char op[2];
int a,b,c;
scanf("%s",op);
if(op[0] == 'Q'){
scanf("%d%d",&a,&b);
printf("%d\n",query(a,b,1,n,1));
}else{
scanf("%d%d%d",&a,&b,&c);
update(a,b,c,1,n,1);
}
}
puts("");//
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: