学线段树的原因是因为cf的一道题目始终想不出来怎么优化,后来知道区间查询和修改要用到线段树。。。
原题:Iva & Pav
线段树的作用
-
区间最值查询:可以高效地找到给定区间内的最大值、最小值等。
-
区间和查询:可以高效地计算给定区间内元素的和、积等。
-
区间更新:可以高效地对给定区间内的元素进行更新操作,如增加一个固定值、赋值等。
-
区间覆盖:可以将给定区间内的元素全部赋值为一个固定值。
-
区间合并:可以将多个区间合并成一个区间,快速地进行区间合并操作。
-
区间离散化:可以将区间内的元素进行离散化处理,方便进行查询和统计操作。
-
区间交集:可以快速地找到多个区间之间的交集
线段树和树状数组的区别
刚学完树状数组来学线段树,一开始还不知道他们具体的差别在哪里,那么以下是我的理解。
1.树状数组是前缀和优化,要用到前缀和的时候较为方便。
2.树状数组用来进行单点修改,区间查询;或者区间修改,单点查询较为方便,而区间查询和区间修改较为复杂,因此可以用线段树优化。
3.线段树适用于需要频繁的区间查询和更新操作的问题,如区间最值、区间和等,能够灵活处理各种区间操作。
4.树状数组适用于一维数组的前缀和查询和更新操作,对于简单的区间操作也能够提供高效的解决方案。
例题:
最大数
直接看代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
//单点插入,区间查询
const int N = 2e5+5;
struct node{
int l,r;
int v;
}tr[N*4];
int m,p;
//子节点的信息更新父节点
void pushup(int u){
tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
}
//u为当前线段树节点编号
void build(int u,int l,int r){
tr[u]={l,r};
if(l==r)return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
}
//查询以u为根节点,区间[l,r]中的最大值
int query(int u, int l, int r) {
// Tl-----Tr
// L-------------R
//1.不必分治,直接返回
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
int mid = tr[u].l + tr[u].r >> 1;
int v = 0;
// Tl----m----Tr
// L-------------R
//2.需要在tr的左区间[Tl, m]继续分治
if(l <= mid) v = query(u << 1, l, r);
// Tl----m----Tr
// L---------R
//3.需要在tr的右区间(m, Tr]继续分治
if(r > mid) v = max(v, query(u << 1 | 1, l, r));
// Tl----m----Tr
// L-----R
//2.3涵盖了这种情况
return v;
}
//u为节点编号,x为修改位置,v为修改的值
void modify(int u,int x,int v){
if(tr[u].l==tr[u].r)tr[u].v=v;//叶子节点,递归出口
else{
int mid=tr[u].l+tr[u].r>>1;
//分治,修改位置偏左往左边遍历,偏右往右边遍历
if(x<=mid)modify(u<<1,x,v);
else {
modify(u<<1|1,x,v);
}
pushup(u);//回溯,子节点的信息更新父节点
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
//n表示树中节点个数,last表示上一次查询的结果
int n=0,last=0;
cin>>m>>p;
//初始化线段树,节点的区间最多为[1,m]
build(1,1,m);
while(m--){
char op;cin>>op;
if(op=='A'){//添加节点
int t;cin>>t;
//在n+1处插入
modify(1,n+1,(t+last)%p);
//节点个数+1
n++;
}
else {
int l;cin>>l;
//查询[n - L + 1, n]内的最大值,u = 1,即从根节点开始查询
last=query(1,n-l+1,n);
cout<<last<<"\n";
}
}
return 0;
}
你能回答这些问题吗
如图:
如图,假设我们要求区间的最大子段和,有三种情况:
1.包含所有左半边,部分右半边----->左半边的区间和+右半边的前缀和
2.包含所有右半边,部分左半边----->右半边的区间和+左半边的后缀和
3.中间的一部分----->左半边的后缀和+右半边的前缀和
因此我们的结构体要记录四个信息:
struct node{
int l,r;
int sum;//[l,r]的区间和
int lmax;//最大前缀和
int rmax;//最大后缀和
int tmax;//区间[l,r]最大连续子段和
}tr[N*4];
同时pushup函数根据上图可以推出:(重载函数)
//u表示该节点,l表示该节点的左子树,r表示该节点的右子树
void pushup(node &u,node &l,node &r){
u.sum=l.sum+r.sum;
//三种最大连续子段和的情况
u.lmax=max(l.lmax,l.sum+r.lmax);
u.rmax=max(r.rmax,r.sum+l.rmax);
u.tmax=max({l.tmax,r.tmax,l.rmax+r.lmax});
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
代码附上:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e5+5;
int w[N];
int n,m;
struct node{
int l,r;
int sum;//[l,r]的区间和
int lmax;//最大前缀和
int rmax;//最大后缀和
int tmax;//区间[l,r]最大连续子段和
}tr[N*4];
//u表示该节点,l表示该节点的左子树,r表示该节点的右子树
void pushup(node &u,node &l,node &r){
u.sum=l.sum+r.sum;
//三种最大连续子段和的情况
u.lmax=max(l.lmax,l.sum+r.lmax);
u.rmax=max(r.rmax,r.sum+l.rmax);
u.tmax=max({l.tmax,r.tmax,l.rmax+r.lmax});
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
if(l==r)tr[u]={l,r,w[r],w[r],w[r],w[r]};//找到叶子节点
else{
tr[u]={l,r};//设当前区间为[l,r]
int mid=l+r>>1;
build(u<<1,l,mid);//左子树
build(u<<1|1,mid+1,r);//右子树
pushup(u);//修改父节点
}
}
//每次从1号节点开始找,找到位置位于x的数,并把它修改为v
void modify(int u,int x,int v){
if(tr[u].l==x && tr[u].r==x)tr[u]={x,x,v,v,v,v};
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid)modify(u<<1,x,v);//x位于当前区间的左半子区间
else modify(u<<1|1,x,v);//x位于当前区间的右半子区间
pushup(u);//修改父节点的相关信息
}
}
node query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r)return tr[u];//被包含
else{
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid)return query(u<<1,l,r);//查询左半区间
else if(l>mid)return query(u<<1|1,l,r);//查询右半区间
else{//横跨左右区间
auto left=query(u<<1,l,r);
auto right=query(u<<1|1,l,r);
node res;
pushup(res,left,right);
return res;
}
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>w[i];
build(1,1,n);//建树
int k,x,y;
while(m--){
cin>>k>>x>>y;
if(k==1){//查询
if(x>y)swap(x,y);
cout<<query(1,x,y).tmax<<"\n";
}
else modify(1,x,y);//修改
}
return 0;
}