ZKW线段树,非递归写法,据说常数很小,其实早就想写一篇了,但是还没发现他能派上用场的题目,比如什么卡线段树的,今天恰好有一个noip2012D2T2,就顺便一起写了。
这里就不挂题目了,直接看大意吧。
题目大意:给定序列,支持区间减少操作,当序列中有负数时停止,输出次数。
数据范围:序列元素个数 n<=106 ,修改次数 m<=106
这题正解是二分,这里先放下正解,稍后再说。
一看这就是线段树,区间修改lazy,但是数据范围较大,会卡一般的线段树,当然也有常数好的线段树,比如(@yhr)就怎么写常数都小,当时据说noip2012被卡成七八十分的人很多。
下面可以说正题了
ZKW线段树
首先应该先去看一下《统计的力量》,那里给出了较详细的思路和做法,但是也有不充分的地方,据说代码还有错的。。。
ZKW线段树的主要思路就是将原来的递归实现的线段树利用特有的性质 1 换为类似堆式储存结构 2 ,然后将自顶向下的递归变成自下而上的方法。
1 . 线段树如果用堆结构表示的话长成这样:
这里ZKW发现一个性质:这个标号如果用二进制表示:
这里的任意一子节点的标号是其父节点为前缀的,利用这就容易找到其父节点了,自下而上的步骤已经完成一半了。
2 . 由于线段树不是一颗满二叉树,那么我们想用堆存,就得把它用一个满二叉树存,这样将原数组信息存在叶子节点,由于堆特点:第i层节点开始标号: 2i−1 处理出一个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;
}