好久没写博客了。。。。。
我登进CSDN。。。
开始写博客
线段树:一种神奇的的东东。。
可以进行区间的各种操作
是一种基于二分思想的二叉树结构,用于区间进行信息统计,比树状数组更加通用的一个东东
线段树的基本结构:
1,线段树每个节点表示一个区间
2,线段树具有唯一的根节点,代表的区间是整个统计范围,如[1,n]
3,线段树的每个叶节点都代表一个长度为1的元区间[x,x]
4,对于每个节点[l,r],其左子树节点为[l,mid],右子树节点为[mid+1,r](mid=(l+r)/2)
如图二叉树的视角
二叉树的建树
在建树的时候可以维护很多东东,在下面这段代码是维护区间的累加和
struct cow
{
int l,r;//区间为l到r这一段
long long sum;//区间累加和
#define l(x) tree[x].l
#define r(x) tree[x].r
#define sum(x) tree[x].sum
//有点懒
}tree[4*N];
void build(int p,int l,int r)
{
//l,r如上所述
//p为所在的的这个区间编号
l(p)=l;r(p)=r;
if(l==r){sum(p)=a[l];return ;}
int mid=(l+r)/2;
build(p*2,l,mid);//分治开始
build(p*2+1,mid+1,r);
sum(p)=sum(p*2)+sum(p*2+1);
//sum累加
}
build(1,1,n)
线段树的单点修改
先找到叶节点,修改之后一层层向上回溯
void change(int p,int x,int v)
{//x表示要修改的位置,v表示修改成的值
if(r(p)==x&&l(p)==x){sum(p)=v;return ;}
//找到位置,把要赋值的全赋一遍,跳出
int mid=(l(p)+r(p))/2;//寻找中间位置
if(x>mid)change(p*2+1,x,v);
else change(p*2,x,v);//两遍递归
sum(p)=sum(p*2)+sum(p*2+1);//回溯
}
其时间复杂度为O(log(n))
线段树区间查询
举个栗子:
要查询l~r的区间总和,操作顺序如下:
1,先从1~n开始递归
2,若l~r覆盖了当前的节点,回溯把这个加上
3,判断左子树是否存在,如果存在,递归进去,不在,say bye bye
4,判断右子树是否存在,如果存在,递归进去,不在,say bye bye
代码如下:
long long ask(int p,int l,int r)//查询l~r的区间和
{
if(l<=l(p)&&r>=r(p))return sum(p);//完全覆盖
long long o=0;
int mid=(l(p)+r(p))/2;//折中
if(l<=mid)o+=ask(p*2,l,r);//判断左子树是否存在
if(r>mid)o+=ask(p*2+1,l,r);//判断右子树是否存在
return o;//返回
}
aks(1,l,r);
其时间复杂度为O(log(n))
下面来道例题感受一下
题目描述
给定长度为N的数列A,以及M条指令 (N≤≤500000, M≤≤100000),每条指令可能是以下两种之一:
“2 x y”,把 A[x] 改成 y。
“1 x y”,查询区间 [x,y] 中的最大连续子段和,即maxx≤l≤r≤y∑ri=lA[i]maxx≤l≤r≤y∑i=lrA[i]。 对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数N,M
第二行N个整数Ai
接下来M行每行3个整数k,x,y,k=1表示查询(此时如果x>y,请交换x,y),k=2表示修改
输出格式
对于每个询问输出一个整数表示答案。
样例数据
input
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
output
2
-1
数据规模与约定
对于100%的数据: N≤≤500000, M≤≤100000, |Ai|≤≤1000
时间限制:10s10s
空间限制:256MB
这道题的主要难点是在于要求区间最大的连续段子和,因为是分治的思想,我们不妨假设l~mid和mid+1~r已经被求出
我们可以维护四个信息:
1,sum区间和
2,ans区间最大段子和
3,lamx靠近左端的区间最大段子和
4,rmax靠近右端的区间最大段子和
我们只需要在build函数和chane上改一下就行了
sum(p)=sum(p*2)+sum(p*2+1);
lm(p)=max(lm(p*2),lm(p*2+1)+sum(p*2));
rm(p)=max(rm(p*2+1),rm(p*2)+sum(p*2+1));
ans(p)=max(max(ans(p*2),ans(p*2+1)),rm(p*2)+lm(p*2+1));
(注意:要在ask里用结构体下传)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=500001;
struct cow
{
int l,r;
long long sum,lmax,rmax,ans;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define sum(x) tree[x].sum
#define ans(x) tree[x].ans
#define lm(x) tree[x].lmax
#define rm(x) tree[x].rmax
}tree[4*N];
int n,m,a[N];
void build(int p,int l,int r)
{
l(p)=l;r(p)=r;
if(l==r)
{
sum(p)=a[l];
ans(p)=a[l];
lm(p)=a[l];
rm(p)=a[l];
return ;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
sum(p)=sum(p*2)+sum(p*2+1);
lm(p)=max(lm(p*2),lm(p*2+1)+sum(p*2));
rm(p)=max(rm(p*2+1),rm(p*2)+sum(p*2+1));
ans(p)=max(max(ans(p*2),ans(p*2+1)),rm(p*2)+lm(p*2+1));
}
cow ask(int p,int l,int r)
{
if(l(p)>=l&&r(p)<=r) return tree[p];
int mid=l(p)+r(p)>>1;
if(mid<l)return ask(p<<1|1,l,r);
if(mid>=r)return ask(p<<1,l,r);
cow o1,o2,omax;
o1=ask(p<<1,l,r);
o2=ask(p<<1|1,l,r);
omax.ans=max(max(o1.ans,o2.ans),o1.rmax+o2.lmax);
omax.lmax=max(o1.lmax,o1.sum+o2.lmax);
omax.rmax=max(o2.rmax,o2.sum+o1.rmax);
return omax;
}
void change(int p,int x,int v)
{
if(r(p)==x&&l(p)==x){sum(p)=ans(p)=lm(p)=rm(p)=v;return ;}
int mid=(l(p)+r(p))/2;
if(x>mid)change(p*2+1,x,v);
else change(p*2,x,v);
sum(p)=sum(p*2)+sum(p*2+1);
lm(p)=max(lm(p*2),lm(p*2+1)+sum(p*2));
rm(p)=max(rm(p*2+1),rm(p*2)+sum(p*2+1));
ans(p)=max(max(ans(p*2),ans(p*2+1)),rm(p*2)+lm(p*2+1));
return ;
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
cin>>n;cin>>m;
for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n);
for(int i=1;i<=m;i++)
{
int x,y,z;cin>>x>>y>>z;
if(x==1)
{
if(y>z)swap(y,z);
cow u=ask(1,y,z);
cout<<u.ans<<endl;
}
else change(1,y,z);
}
return 0;
}
延迟标记
先来道例题:
题目描述
对于数列 A1, A2, ... , AN. 你要进行2个操作:将一个区间的数同加上某个数,输出一段区间的和。
输入格式
第一行2个整数表示数列长度和操作次数. 1 ≤ N,Q ≤ 100000. 第二行为数列 A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000. 接下来的Q行操作: "C a b c" 表示将 Aa, Aa+1, ... , Ab.加上c. -10000 ≤ c ≤ 10000. "Q a b" 输出区间[a,b]的和。
输出格式
输出所有询问的答案,每行1个。
样例数据
input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
output
4
55
9
15
数据规模与约定
时间限制:5s5s
空间限制:128MB
这道题你们会说不就线段树暴力就行了吗?
我呵呵呵
又有人说树状数组
我们讲的是线段树啊
思路:
如果暴力,超时妥妥的。。。
现在就要延迟标记了。
我们可以打一个延迟标记,很简单
在维护信息时用一个id表示这段区间要加的但是没加的数,递归道这的时候再跟新这个标记
像这样:
void add(int p)
{
if(id(p))
{
sum(p*2)+=(r(p*2)-l(p*2)+1)*id(p);
sum(p*2+1)+=(r(p*2+1)-l(p*2+1)+1)*id(p);
id(p*2)+=id(p);
id(p*2+1)+=id(p);
id(p)=0;
}
}
注意一定要随时想的更新标记,不少写一个就别想调出来了。。。
延迟标记可以优化很多无用的相加,很有用(主要是来用优化时间)
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
struct cow
{
int l,r;
long long sum,id;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define sum(x) tree[x].sum
#define id(x) tree[x].id
}tree[4*N];
int n,m,a[N];
void build(int p,int l,int r)
{
l(p)=l;r(p)=r;
if(l==r){sum(p)=a[l];return ;}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
sum(p)=sum(p*2)+sum(p*2+1);
}
void add(int p)
{
if(id(p))
{
sum(p*2)+=(r(p*2)-l(p*2)+1)*id(p);
sum(p*2+1)+=(r(p*2+1)-l(p*2+1)+1)*id(p);
id(p*2)+=id(p);
id(p*2+1)+=id(p);
id(p)=0;
}
}
long long ask(int p,int l,int r)
{
if(l<=l(p)&&r>=r(p))return sum(p);
add(p);long long o=0;
int mid=(l(p)+r(p))/2;
if(l<=mid)o+=ask(p*2,l,r);
if(r>mid)o+=ask(p*2+1,l,r);
return o;
}
void change(int p,int l,int r,int c)
{
if(l<=l(p)&&r>=r(p))
{
id(p)+=c;
sum(p)+=(long long)(r(p)-l(p)+1)*c;
return ;
}
add(p);
int mid=(l(p)+r(p))/2;
if(r>mid)change(p*2+1,l,r,c);
if(l<=mid)change(p*2,l,r,c);
sum(p)=sum(p*2)+sum(p*2+1);
return ;
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n);
for(int i=1;i<=m;i++)
{
char z[2];cin>>z;
if(z[0]=='Q')
{
int x,y;cin>>x>>y;
cout<<(long long)ask(1,x,y)<<endl;
}
else
{
int x,y,c;
cin>>x>>y>>c;
change(1,x,y,c);
}
}
return 0;
}
OK,线段树博大精深,还有扫描线等一些高深的操作,本蒟蒻还没弄懂,这里就不在赘述了。。