【线段树】ZKW线段树(noip2012D2T2)(选教室)

ZKW线段树,非递归写法,据说常数很小,其实早就想写一篇了,但是还没发现他能派上用场的题目,比如什么卡线段树的,今天恰好有一个noip2012D2T2,就顺便一起写了。

这里就不挂题目了,直接看大意吧。

题目大意:给定序列,支持区间减少操作,当序列中有负数时停止,输出次数。
数据范围:序列元素个数 n<=106 ,修改次数 m<=106

这题正解是二分,这里先放下正解,稍后再说。

一看这就是线段树,区间修改lazy,但是数据范围较大,会卡一般的线段树,当然也有常数好的线段树,比如(@yhr)就怎么写常数都小,当时据说noip2012被卡成七八十分的人很多。

下面可以说正题了

ZKW线段树

首先应该先去看一下《统计的力量》,那里给出了较详细的思路和做法,但是也有不充分的地方,据说代码还有错的。。。

ZKW线段树的主要思路就是将原来的递归实现的线段树利用特有的性质 1 换为类似堆式储存结构 2 ,然后将自顶向下的递归变成自下而上的方法。

1 . 线段树如果用堆结构表示的话长成这样:
线段树
这里ZKW发现一个性质:这个标号如果用二进制表示:
这里写图片描述
这里的任意一子节点的标号是其父节点为前缀的,利用这就容易找到其父节点了,自下而上的步骤已经完成一半了。

2 . 由于线段树不是一颗满二叉树,那么我们想用堆存,就得把它用一个满二叉树存,这样将原数组信息存在叶子节点,由于堆特点:第i层节点开始标号: 2i1 处理出一个M最为树中信息储存的开始下标就可以了。

这样就可以写出建树的代码:

void build_tree(int n)
{
   for(M=1;M<=n+1;M<<=1);
   memset(T,0,sizeof(int)*(M+M));
   for(int i = 1;i<=n;i++)
      scanf("%d",T+i+M);
   int A;
   for(int i=(n+M)/2;i;i--)
   {
      A = min(T[i+i],T[i+i+1]);T[i+i]-=A;T[i+i+1]-=A;T[i]+=A;
   }
}

和查询的代码:

int query_tree(int s,int t)
{
   int Lans=0,Rans=0,ans=0;
   for(s=s+M,t=t+M;s^t^1;s>>=1,t>>=1)
   {
       Lans += T[s];
      Rans += T[t];
      if(~s&1)Lans = min(Lans,T[s^1]);
      if(t&1) Rans = min(Rans,T[t^1]);
   }
   ans = min(Lans,Rans);
   while(s>1)ans += T[s>>=1];
   return ans;
}

还有单点修改的代码:

void Add(int n,int v)
{
    for(T[n+=M]=v,n>>=1; n; n>>=1) 
        PushUP(n);
}
void PushUP(int rt)
{
    T[rt]=min(T[rt<<1],T[rt<<1|1]);
}

到这里,我们就看完了最基础的部分。。。

还差一个区间修改:
ZKW线段树首先否定下传标记,采用将信息记录在下面的方法,那么具体是怎么实现的呢?

首先,他提到将标记永久化,正所谓值是相对的,标记是绝对的,如果一个线段树开始都是0,那么在赋值的过程中不正是相当于修改吗?

那么就索性将标记永久化,原数组的意义从值变成了当前值减父亲节点的值。

查询的时候就直接查询这个数组就可以,将要查询的节点一直加到根节点, T[n]=M[n]+M[n>>1]++M[1]; 遇到点就进行这个 A=min(M[2x],M[2x+1])M[x]+=A,M[2x]=A,M[2x+1]=A

这时修改就会变得简单,直接修改这个数组的值就行了,因为你加减一个值就像当于修改当前标记,这里就相当于修改这个数组。

代码:

void fix_tree(int s,int t,int x)
{
   int A;
   for(s=s+M-1,t=t+M+1;s^t^1;s>>=1,t>>=1)
   {
       if(~s&1) T[s^1]+=x;
       if(t&1) T[t^1]+=x;
       A = min(T[s],T[s^1]);T[s]-=A;T[s^1]-=A;T[s>>1]+=A;
       A = min(T[t],T[t^1]);T[t]-=A;T[t^1]-=A;T[t>>1]+=A;
}
for(;s>1;s>>=1)
{
   A=min(T[s],T[s^1]);T[s]-=A;T[s^1]-=A;T[s>>1]+=A;
}
}

最后我再放上这道题的完整代码:

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int minn = 2000000;
int T[minn<<2];
int M;
void build_tree(int n)
{
   for(M=1;M<=n+1;M<<=1);
   memset(T,0,sizeof(int)*(M+M));
   for(int i = 1;i<=n;i++)
      scanf("%d",T+i+M);
   int A;
   for(int i=(n+M)/2;i;i--)
   {
      A = min(T[i+i],T[i+i+1]);T[i+i]-=A;T[i+i+1]-=A;T[i]+=A;
   }
}
int query_tree(int s,int t)
{
   int Lans=0,Rans=0,ans=0;
   for(s=s+M,t=t+M;s^t^1;s>>=1,t>>=1)
   {
       Lans += T[s];
      Rans += T[t];
      if(~s&1)Lans = min(Lans,T[s^1]);
      if(t&1) Rans = min(Rans,T[t^1]);
   }
   ans = min(Lans,Rans);
   while(s>1)ans += T[s>>=1];
   return ans;
}
void fix_tree(int s,int t,int x)
{
   int A;
   for(s=s+M-1,t=t+M+1;s^t^1;s>>=1,t>>=1)
   {
       if(~s&1) T[s^1]+=x;
       if(t&1) T[t^1]+=x;
       A = min(T[s],T[s^1]);T[s]-=A;T[s^1]-=A;T[s>>1]+=A;
       A = min(T[t],T[t^1]);T[t]-=A;T[t^1]-=A;T[t>>1]+=A;
}
for(;s>1;s>>=1)
{
   A=min(T[s],T[s^1]);T[s]-=A;T[s^1]-=A;T[s>>1]+=A;
}
}
int main()
{
    int n,m;
    bool flag=0;
    scanf("%d%d",&n,&m);
    build_tree(n);
    for(int i=1;i<=m;i++){
        int d;
        int s,t;
        scanf("%d%d%d",&d,&s,&t);
        fix_tree(s,t,-d);
        if(query_tree(1,n)<0)
        {
            printf("-1\n");
            printf("%d\n",i);
            flag=1;
            break;
        }
    }
    if(!flag)
        printf("0\n");
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值