线段树和树状数组都是一种基于区间的修改查询操作。
这两种修改和查询操作相互影响,不能独立编写。
单值处理,区间查询
结构体完成
struct node
{
int l,r;
ll sum;
};
struct SegmentTree //单点修改,区间查询
{
node tr[maxn<<2];
inline void buildInit(int p)
{
}
inline void push_up(int p)
{
}
inline void changeOp(int p,ll v)
{
}
inline void addOp(int p,ll v)
{
}
inline node queryUnion(node a,node b)
{
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
if(l==r){buildInit(p);return;}
int mid=(l+r)>>1;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
push_up(p);
}
void change(int p,int t,ll v)
{
if(tr[p].l==tr[p].r){changeOp(p,v);return;}
int mid=(tr[p].l+tr[p].r)>>1;
if(t<=mid) change(p<<1,t,v);
else change(p<<1|1,t,v);
push_up(p);
}
void add(int p,int t,ll v)
{
if(tr[p].l==tr[p].r){addOp(p,v);return;}
int mid=(tr[p].l+tr[p].r)>>1;
if(t<=mid) add(p<<1,t,v);
else add(p<<1|1,t,v);
push_up(p);
}
node query(int p,int lt,int rt)
{
if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
int mid=(tr[p].l+tr[p].r)>>1;
if(rt<=mid) return query(p<<1,lt,rt);
if(lt>mid) return query(p<<1|1,lt,rt);
return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
}
}st;
区间修改,单点查询
这一部分没有求和,因为可以看成是区间修改区间查询,并且对单点的区间查询,故求和是没有太大意义的。但是可以有对区间的整体进行一系列的一种操作,并对单点进行查询结果。由于不求和,因此对于树状数组来说不好用,主要是线段树的操作。
最大最小值
对给定区间内每个值进行一个赋值操作,获得当前值与所给值更大或更小的一个,并在之后进行每个值的询问操作。
最底层存储初始值,上层用于进行区间的变化存储!!(线段树上层不一定需要用于存储区间的最终结果以查询时节约时间,单点查询本身并不花时间,上层可以只存储变化结果)
//求最大最小值的建立 max min
ll a[maxn];
ll tree[maxn<<2];
void build(int l,int r,int pos)
{
if(l==r)
{
tree[pos]=a[l];
return ;
}
int mid=(l+r)/2;
build(l,mid,pos<<1);
build(mid+1,r,(pos<<1)+1);
}
//区间修改
void update(int l,int r,int pos,int tl,int tr,ll v)
{
if(l>tr || r<tl)
return ;
if(l>=tl && r<=tr)
{
tree[pos]=max(tree[pos],v);
return ;
}
if(v<tree[pos]) //******剪枝优化******
return ;
int mid=(l+r)/2;
update(l,mid,pos<<1,tl,tr,v);
update(mid+1,r,(pos<<1)+1,tl,tr,v);
}
//查询,向上查询修改过的所有过程
ll query(int l,int r,int pos,int tl,int tr)
{
if(l>tr || r<tl)
return -inf; //有负值记得改成-inf
if(l>=tl && r<=tr)
return tree[pos];
int mid=(l+r)/2;
ll lef=query(l,mid,pos<<1,tl,tr);
ll rig=query(mid+1,r,(pos<<1)+1,tl,tr);
return max(tree[pos],max(lef,rig));
}
结构体完成
优化:
1.剪枝优化。 由于在树上每一个区间代表该区间下面所有区间的值都会被修改成可能的一个值tree[pos],如果需要修改的值在该区间下,并且值比tree[pos]小,则不可能让需要修改的值成为最大值。故可以剪枝。见上述代码
2.RMQ:专门用于处理区间极值问题 https://blog.csdn.net/qq_38890926/article/details/81489495 用该方法处理比正常线段树消耗时间会少一些常数
区间修改,区间查询
求和:
在需要区间修改和取区间查询的时候,需要添加一个lazy数组用于记录查询点之下的点对当前查询点的影响。
其中黑色为查询所到底的部分,黑色、红色和绿色为修改部分,因此我们能够得到绿色和黑色部分的值,但是红色部分的值就得不到,因此我们需要lazy数组存储红色部分的值,当查询到黑色部分时,加上lazy数组中的值来获取红色部分。
线段树
ll a[maxn];
ll biTree[maxn<<2];
ll bcTree[maxn<<2]; //lazy记录下方的量,只有push_down 只影响子树
void push_up(int pos) //只会处理原数组
{
biTree[pos]=biTree[pos<<1] + biTree[pos<<1|1]; //求和用加号
}
void push_down(int l,int r,int pos)
{
if(bcTree[pos])
{
bcTree[pos<<1]=bcTree[pos<<1]+bcTree[pos]; //lazy数组的下放
bcTree[pos<<1|1]=bcTree[pos<<1|1]+bcTree[pos];
int mid=(l+r)>>1;
biTree[pos<<1]=biTree[pos<<1]+(mid-l+1)*bcTree[pos]; //lazy数组对bit树的影响;
biTree[pos<<1|1]=biTree[pos<<1|1]+(r-mid)*bcTree[pos];
bcTree[pos]=0;
}
}
//初始化
void build(int l,int r,int pos)
{
bcTree[pos]=0; //初始化lazy数组
if(l==r) //更新bitree数组
{
biTree[pos]=a[l]; //赋值,如果初始值为0,就赋值为0
return ;
}
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
push_up(pos); //向上更新
}
//区间修改
void add(int l,int r,int pos,int tl,int tr,ll num)
{
if(l>tr || r<tl)
return;
if(l>=tl && r<=tr)
{
bcTree[pos]=bcTree[pos]+num; //当前部分的添加到lazy中
biTree[pos]=biTree[pos]+(r-l+1)*num; //当前部分区间更新结果
return;
}
push_down(l,r,pos); //下放lazy数组
int mid=(l+r)>>1;
add(l,mid,pos<<1,tl,tr,num);
add(mid+1,r,pos<<1|1,tl,tr,num);
push_up(pos);
}
//区间查询
ll query(int l,int r,int pos,int tl,int tr)
{
if(l>tr || r<tl)
return 0;
if(l>=tl && r<=tr)
return biTree[pos]; //当前的lazy数组已被处理,不用处理了
push_down(l,r,pos);
int mid=(l+r)>>1;
ll lef=query(l,mid,pos<<1,tl,tr);
ll rig=query(mid+1,r,pos<<1|1,tl,tr);
return lef+rig;
}
结构体完成
struct node
{
int l,r;
ll sum;
ll val;
};
struct SegmentTree //单点修改,区间查询
{
node tr[maxn<<2];
inline void buildInit(int p)
{
}
inline void push_up(int p)
{
}
inline void push_down(int p)
{
if(tr[p].val!=0)
{
int l=p<<1,r=p<<1|1;
}
}
inline void addOp(int p,ll v)
{
}
inline node queryUnion(node a,node b)
{
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
tr[p].val=0;
if(l==r){buildInit(p);return;}
int mid=(l+r)>>1;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
push_up(p);
}
void add(int p,int lt,int rt,ll v)
{
if(tr[p].l>rt || tr[p].r<lt)return;
if(tr[p].l>=lt && tr[p].r<=rt){addOp(p,v);return;}
push_down(p);
int mid=(tr[p].l+tr[p].r)>>1;
add(p<<1,lt,rt,v);add(p<<1|1,lt,rt,v);
push_up(p);
}
node query(int p,int lt,int rt)
{
if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
push_down(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(rt<=mid) return query(p<<1,lt,rt);
if(lt>mid) return query(p<<1|1,lt,rt);
return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
}
}st;
树状数组
树状数组中的两个操作sum和add操作,由于sum是对一个数组求前缀和,add是对某个单点加上v值,因此无论是否是区间操作,对其没有任何影响。
在调用结果的时候会有不同的调用方式。
l bit0[maxn];
ll bit1[maxn];
//初始化
void init()
{
memset(bit0,0,sizeof(bit0));
memset(bit1,0,sizeof(bit1));
}
//返回最低位1所在的值
int lowbit(int i)
{
return i&(-i);
}
//前缀和求和
ll sum(ll *a,int i)
{
ll s=0;
while(i)
{
s=s+a[i];
i=i-lowbit(i); //求和向前求和
}
return s;
}
//添加某个值到某一位置上
void add(ll *a,int i,ll v)
{
while(i<=n)
{
a[i]=a[i]+v; //添加向上添加
i=i+lowbit(i);
}
}
int main()
{
//目前还不知道怎么操作呀呀呀呀呀GGGGGGGGGG
}
set值类型的树状数组
//set种类,并且求最后的种类数 poj2777
int n;
int a[maxn];
int biTree[maxn<<2];
int bcTree[maxn<<2]; //lazy记录下方的量,只有push_down 只影响子树
void push_up(int pos) //只会处理原数组
{
biTree[pos]=biTree[pos<<1] | biTree[pos<<1|1]; //求和用加号,符号要随着需求而定
}
void push_down(int tl,int tr,int pos)
{
if(bcTree[pos])
{
bcTree[pos<<1]=bcTree[pos];
bcTree[pos<<1|1]=bcTree[pos];
biTree[pos<<1]=bcTree[pos];
biTree[pos<<1|1]=bcTree[pos];
bcTree[pos]=0;
}
}
//初始化
void build(int l,int r,int pos)
{
bcTree[pos]=0; //初始化lazy数组
if(l==r) //更新bitree数组
{
biTree[pos]=a[l]; //赋值,如果初始值为0,就赋值为0
return ;
}
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
push_up(pos); //向上更新
}
//区间设置
void setTree(int l,int r,int pos,int tl,int tr,int num)
{
if(l>tr || r<tl)
return;
if(l>=tl && r<=tr)
{
bcTree[pos]=1<<(num-1); //在设置里面lazy数组表示当前之下的所有情况
biTree[pos]=(1<<(num-1)); //当前部分的添加
return;
}
push_down(l,r,pos); //下放lazy数组
int mid=(l+r)>>1;
setTree(l,mid,pos<<1,tl,tr,num);
setTree(mid+1,r,pos<<1|1,tl,tr,num);
push_up(pos);
}
//区间查询
int query(int l,int r,int pos,int tl,int tr)
{
if(l>tr || r<tl)
return 0;
if(l>=tl && r<=tr)
return biTree[pos]; //当前的lazy数组已被处理,不用处理了
push_down(l,r,pos);
int mid=(l+r)>>1;
int lef=query(l,mid,pos<<1,tl,tr);
int rig=query(mid+1,r,pos<<1|1,tl,tr);
return lef|rig; //注意更改符号 按照需求
}
离散化区间bit和结构体存储数据lazy树
该段代码基本显示了线段树整体的本质和总的基本功能,以及lazy的用处
int n;
struct area
{
int l,r;
};
area q[maxn];
struct Discretization
{
vector<int> mp;
int len;
void clear(){mp.clear();len=0;}
void add(int val){mp.push_back(val);}
void discrete()
{
sort(mp.begin(),mp.end());
mp.resize(unique(mp.begin(),mp.end())-mp.begin());
len=mp.size();
}
int getPos(int val)
{
int pos=lower_bound(mp.begin(),mp.end(),val)-mp.begin();
if(pos==len)return -1;
else return pos;
}
int getVal(int pos){return mp[pos];}
}disc;
struct node
{
int l,r;
int sum;
int isl,isr;
int lazy;
};
struct SegmentTree //单点修改,区间查询
{
node tr[maxn<<2];
inline void buildInit(int p)
{
tr[p].sum=0;
tr[p].isl=tr[p].isr=0;
}
inline void push_up(int p)
{
int l=p<<1,r=p<<1|1;
if(tr[l].isr+tr[r].isl!=2)tr[p].sum=tr[l].sum+tr[r].sum;
else tr[p].sum=tr[l].sum+tr[r].sum-1;
tr[p].isl=tr[l].isl;
tr[p].isr=tr[r].isr;
}
inline void push_down(int p)
{
if(tr[p].lazy!=0)
{
int l=p<<1,r=p<<1|1;
tr[l].sum=tr[r].sum=1;
tr[l].isl=tr[l].isr=tr[r].isl=tr[r].isr=1;
tr[l].lazy=tr[r].lazy=1;
tr[p].lazy=0;
}
}
inline void addOp(int p,int v)
{
tr[p].isl=tr[p].isr=1;
tr[p].sum=1;
tr[p].lazy=1;
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
tr[p].lazy=0;
if(l==r){buildInit(p);return;}
int mid=(l+r)>>1;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
push_up(p);
}
void add(int p,int lt,int rt,int v)
{
if(tr[p].l>rt || tr[p].r<lt)return;
if(tr[p].l>=lt && tr[p].r<=rt){addOp(p,v);return;}
push_down(p);
int mid=(tr[p].l+tr[p].r)>>1;
add(p<<1,lt,rt,v);add(p<<1|1,lt,rt,v);
push_up(p);
}
}st;
int main()
{
cin>>n;
disc.clear();
for(int i=1;i<=n;i++)
{
cin>>q[i].l>>q[i].r;
q[i].l*=2;
q[i].r*=2;
disc.add(q[i].l);disc.add(q[i].l-1);disc.add(q[i].l+1);
disc.add(q[i].r);disc.add(q[i].r-1);disc.add(q[i].r+1);
}
disc.discrete();
st.build(0,disc.len,1);
for(int i=1;i<=n;i++)
{
int l=q[i].l,r=q[i].r;
l=disc.getPos(l);
r=disc.getPos(r);
st.add(1,l,r,1);
printf("%d%c",st.tr[1].sum,i==n?'\n':' ');
}
return 0;
}
区间合并
在处理取件问题的时候,上层一定需要处理清楚和下层之间的关系,在区间合并的时候记得处理两个区间喝起来之后具有的共性。
POJ - 3667 记得剪枝!!!即向左,向右走的时候如果已经找到结果就不处理另外一部分。
struct bit
{
int lef;
int rig;
int maxnum;
};
bit biTree[maxn<<2];
int lazy[maxn<<2];
void push_up(int l,int r,int pos)
{
int lef=pos<<1;
int rig=pos<<1|1;
int mid=(l+r)>>1;
int dis_lef=mid-l+1;
int dis_rig=r-mid;
if(dis_lef==biTree[lef].lef)
biTree[pos].lef=biTree[lef].lef+biTree[rig].lef;
else
biTree[pos].lef=biTree[lef].lef;
if(dis_rig==biTree[rig].rig)
biTree[pos].rig=biTree[rig].rig+biTree[lef].rig;
else
biTree[pos].rig=biTree[rig].rig;
biTree[pos].maxnum=max(biTree[lef].rig+biTree[rig].lef,max(biTree[lef].maxnum,biTree[rig].maxnum));
}
void push_down(int l,int r,int pos)
{
if(lazy[pos]!=0)
{
int lef=pos<<1;
int rig=pos<<1|1;
int mid=(l+r)>>1;
if(lazy[pos]==1)
{
biTree[lef].lef=biTree[lef].rig=biTree[lef].maxnum=0;
biTree[rig].lef=biTree[rig].rig=biTree[rig].maxnum=0;
lazy[lef]=lazy[rig]=1;
lazy[pos]=0;
return;
}
if(lazy[pos]==2)
{
biTree[lef].lef=biTree[lef].rig=biTree[lef].maxnum=mid-l+1;
biTree[rig].lef=biTree[rig].rig=biTree[rig].maxnum=r-mid;
lazy[lef]=lazy[rig]=2;
lazy[pos]=0;
return;
}
}
}
void build(int l,int r,int pos)
{
biTree[pos].lef=biTree[pos].rig=biTree[pos].maxnum=r-l+1;
lazy[pos]=0;
if(l==r)
return;
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
}
int query(int l,int r,int pos,int dis)
{
if(biTree[pos].maxnum<dis)
return 0;
if(l==r)
return 0;
push_down(l,r,pos);
int mid=(l+r)>>1;
int lef=query(l,mid,pos<<1,dis);
if(lef>0)
return lef;
if(biTree[pos<<1].rig+biTree[pos<<1|1].lef>=dis)
return mid-biTree[pos<<1].rig+1;
int rig=query(mid+1,r,pos<<1|1,dis);
if(rig>0)
return rig;
return 0;
}
void update(int l,int r,int pos,int tl,int tr,int op)
{
if(r<tl || l>tr)
return ;
if(l>=tl && r<=tr)
{
if(op==0)
{
biTree[pos].lef=biTree[pos].rig=biTree[pos].maxnum=0;
lazy[pos]=1;
}
else
{
biTree[pos].lef= biTree[pos].rig=biTree[pos].maxnum=r-l+1;
lazy[pos]=2;
}
return;
}
push_down(l,r,pos);
int mid=(l+r)>>1;
update(l,mid,pos<<1,tl,tr,op);
update(mid+1,r,pos<<1|1,tl,tr,op);
push_up(l,r,pos);
}
区间非重复元素的和
现在给出一系列N个数字A1,A2,...,AN和一些查询(i,j)(1≤i≤j≤N)。对于每个Query(i,j),您要计算子序列Ai,Ai + 1,...,Aj中不同值的总和。
这个题目看过去的时候和线段树很相似,但是其实这个题的本质上是需要莫队进行处理的,因为线段树无法处理。
也即是线段树无法处理在一段区间查询中要丢掉一些中间点的情况,且丢掉的点随着区间不同结果也不同。
莫队算法:https://blog.csdn.net/qq_38890926/article/details/81435072
区间单调上升序列的求和
考虑数列 1 3 5 7 2 2 4 4 4 3,求取满足且i>k的子序列长度为m的序列数。
例如,对于第一个4来说,以它结束的长度为m的子序列数,应该等于它之前每一个小于4且长度为m-1的子序列总数个数和。
需要对每一个这样的数进行以上操作,我们就自然想到用线段树记录这样的总和以使得寻找k=[1,i-1]的这层循环时间降低到 logn
我们要求和的目标是比其小的数,且在之前已经访问过的。已经访问过这一层排序其实本质是访问次序的排序,故就是a序列按照正常序列顺序访问就好。比其小即我们按照从小到大排序,按照排序后的顺序去映射到线段树中的位置,我们就能直接求和该序列之前所有的值,从而得到既在之前已经访问又比其小的值的总和值。
见上升子序列1:https://blog.csdn.net/qq_38890926/article/details/81449455
二维线段树和树状数组
二维的线段树和树状数组本质上就是很多棵线段树或树状数组,在求取有关sum的条件时,如果说我们采用线段树,可能会导致占用内存空间过大,这个时候通常我们会采用树状数组来提高访问效率和空间优化。同理线段树也可以处理非sum以外操作。
线段树
极其浪费空间,几乎不用!!!!
ll tree[maxm][maxn<<2]; //有n个这样的树状数组,每个都可以存储m个数的区间和
//添加(tl=tr) 支持单点修改,不支持区间修改
void add(int l,int r,int pos,int tl,int tr,int v,int len)
{
if(l>tr || r<tl)
return ;
if(l>=tl && r<=tr)
{
tree[len][pos]=tree[len][pos]+v;
return ;
}
tree[len][pos]=tree[len][pos]+v;
int mid=(l+r)/2;
add(l,mid,pos<<1,tl,tr,v,len);
add(mid+1,r,(pos<<1)|1,tl,tr,v,len);
}
//查询
ll sum(int l,int r,int pos,int tl,int tr,int len)
{
if(l>tr || r<tl)
return 0;
if(l>=tl && r<=tr)
return tree[len][pos];
int mid=(l+r)/2;
ll lef=sum(l,mid,pos<<1,tl,tr,len);
ll rig=sum(mid+1,r,(pos<<1)|1,tl,tr,len);
return lef+rig;
}
int main()
{
add(1,n,1,l,r,v,len); //该处l=r
sum(1,n,1,l,r,len);
}
带set和add的二维线段树
struct l
{
int flag;
int v;
};
int treesum[maxm][maxn<<2];
int treemax[maxm][maxn<<2];
int treemin[maxm][maxn<<2];
l lazy[maxm][maxn<<2];
void build(int l,int r,int pos,int row)
{
if(l==r)
{
treemax[row][pos]=treemin[row][pos]=treesum[row][pos]=lazy[row][pos].v=lazy[row][pos].flag=0;
return;
}
treemax[row][pos]=treemin[row][pos]=treesum[row][pos]=lazy[row][pos].v=lazy[row][pos].flag=0;
int mid=(l+r)>>1;
build(l,mid,pos<<1,row);
build(mid+1,r,pos<<1|1,row);
}
void push_up(int pos,int row)
{
treemax[row][pos]=max(treemax[row][pos<<1],treemax[row][pos<<1|1]);
treemin[row][pos]=min(treemin[row][pos<<1],treemin[row][pos<<1|1]);
treesum[row][pos]=treesum[row][pos<<1]+treesum[row][pos<<1|1];
}
void push_down(int l,int r,int pos,int row)
{
if(lazy[row][pos].flag!=0)
{
int lef=pos<<1;
int rig=pos<<1|1;
int mid=(l+r)>>1;
if(lazy[row][pos].flag==1)
{
treesum[row][lef]=treesum[row][lef]+(mid-l+1)*lazy[row][pos].v;
treesum[row][rig]=treesum[row][rig]+(r-mid)*lazy[row][pos].v;
treemax[row][lef]=treemax[row][lef]+lazy[row][pos].v;
treemax[row][rig]=treemax[row][rig]+lazy[row][pos].v;
treemin[row][lef]=treemin[row][lef]+lazy[row][pos].v;
treemin[row][rig]=treemin[row][rig]+lazy[row][pos].v;
if(lazy[row][lef].flag==0)
lazy[row][lef].flag=1;
lazy[row][lef].v=lazy[row][lef].v+lazy[row][pos].v;
if(lazy[row][rig].flag==0)
lazy[row][rig].flag=1;
lazy[row][rig].v=lazy[row][rig].v+lazy[row][pos].v;
lazy[row][pos].v=0;
lazy[row][pos].flag=0;
}
if(lazy[row][pos].flag==2)
{
treesum[row][lef]=(mid-l+1)*lazy[row][pos].v;
treesum[row][rig]=(r-mid)*lazy[row][pos].v;
treemax[row][lef]=lazy[row][pos].v;
treemax[row][rig]=lazy[row][pos].v;
treemin[row][lef]=lazy[row][pos].v;
treemin[row][rig]=lazy[row][pos].v;
lazy[row][lef].flag=2;
lazy[row][lef].v=lazy[row][pos].v;
lazy[row][rig].flag=2;
lazy[row][rig].v=lazy[row][pos].v;
lazy[row][pos].v=0;
lazy[row][pos].flag=0;
}
}
}
void add(int l,int r,int pos,int tl,int tr,int row,int v)
{
if(tl>r || tr<l)
return ;
if(l>=tl && r<=tr)
{
treesum[row][pos]=treesum[row][pos]+(r-l+1)*v;
treemax[row][pos]=treemax[row][pos]+v;
treemin[row][pos]=treemin[row][pos]+v;
lazy[row][pos].v=lazy[row][pos].v+v;
if(lazy[row][pos].flag==0)
lazy[row][pos].flag=1;
return;
}
push_down(l,r,pos,row);
int mid=(l+r)>>1;
add(l,mid,pos<<1,tl,tr,row,v);
add(mid+1,r,pos<<1|1,tl,tr,row,v);
push_up(pos,row);
}
void sset(int l,int r,int pos,int tl,int tr,int row,int v)
{
if(tl>r || tr<l)
return ;
if(l>=tl && r<=tr)
{
treesum[row][pos]=(r-l+1)*v;
treemax[row][pos]=v;
treemin[row][pos]=v;
lazy[row][pos].v=v;
lazy[row][pos].flag=2;
return;
}
push_down(l,r,pos,row);
push_down(l,r,pos,row);
int mid=(l+r)>>1;
sset(l,mid,pos<<1,tl,tr,row,v);
sset(mid+1,r,pos<<1|1,tl,tr,row,v);
push_up(pos,row);
}
int query_sum(int l,int r,int pos,int tl,int tr,int row)
{
if(tl>r || tr<l)
return 0;
if(l>=tl && r<=tr)
{
return treesum[row][pos];
}
push_down(l,r,pos,row);
int mid=(l+r)>>1;
int lef=query_sum(l,mid,pos<<1,tl,tr,row);
int rig=query_sum(mid+1,r,pos<<1|1,tl,tr,row);
return lef+rig;
}
int query_max(int l,int r,int pos,int tl,int tr,int row)
{
if(tl>r || tr<l)
return 0;
if(l>=tl && r<=tr)
{
return treemax[row][pos];
}
push_down(l,r,pos,row);
int mid=(l+r)>>1;
int lef=query_max(l,mid,pos<<1,tl,tr,row);
int rig=query_max(mid+1,r,pos<<1|1,tl,tr,row);
return max(lef,rig);
}
int query_min(int l,int r,int pos,int tl,int tr,int row)
{
if(tl>r || tr<l)
return inf;
if(l>=tl && r<=tr)
{
return treemin[row][pos];
}
push_down(l,r,pos,row);
int mid=(l+r)>>1;
int lef=query_min(l,mid,pos<<1,tl,tr,row);
int rig=query_min(mid+1,r,pos<<1|1,tl,tr,row);
return min(lef,rig);
}
int r,c,m;
int main()
{
while(scanf("%d %d %d",&r,&c,&m)!=EOF)
{
for(int i=1;i<=r;i++)
build(1,c,1,i);
int op;
int r1,w1,r2,w2;
int v;
for(int t=1;t<=m;t++)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d %d %d %d %d",&r1,&w1,&r2,&w2,&v);
for(int i=r1;i<=r2;i++)
add(1,c,1,w1,w2,i,v);
continue;
}
if(op==2)
{
scanf("%d %d %d %d %d",&r1,&w1,&r2,&w2,&v);
for(int i=r1;i<=r2;i++)
sset(1,c,1,w1,w2,i,v);
continue;
}
if(op==3)
{
scanf("%d %d %d %d",&r1,&w1,&r2,&w2);
int sum=0,maxnum=0,minnum=inf;
for(int i=r1;i<=r2;i++)
{
sum=sum+query_sum(1,c,1,w1,w2,i);
maxnum=max(maxnum,query_max(1,c,1,w1,w2,i));
minnum=min(minnum,query_min(1,c,1,w1,w2,i));
}
printf("%d %d %d\n",sum,minnum,maxnum);
}
}
}
return 0;
}
老司机树(Set的优化方法)
老司机树即关心线段树的最后一层,并且由于最后一层满足l,r单调递增的性质,因此可以存到set中进行查询,在处理修改等操作时利用set将大部分点合并以降低查询的复杂度。其他操作都是暴力查询点,一个一个解决。
struct node //结点
{
int l,r;
mutable ll v; //mutable才能修改set中的值
node(int L,int R=-1,ll V=0):l(L),r(R),v(V){}
bool operator<(const node& b)const{return l<b.l;}
};
struct ODT
{
set<node> s;
inline void init(){s.clear();}
inline void insert(node a){s.insert(a);}
set<node>::iterator split(int pos) //分割操作
{
set<node>::iterator it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)return it;
it--;
if(pos>it->r)return s.end();
int l=it->l,r=it->r;
ll v=it->v;
s.erase(it);
s.insert(node(l,pos-1,v));
return s.insert(node(pos,r,v)).first;
}
void assign(int l,int r,ll val)//区间赋值,合并操作
{
set<node>::iterator itr=split(r+1),itl=split(l);
s.erase(itl,itr); //删除itl-itr中间的一段
s.insert(node(l,r,val));
}
void add(int l,int r,ll val)
{
set<node>::iterator itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++)itl->v=itl->v+val;
}
ll rank(int l,int r,int k)
{
vector<pair<ll,int> >vp;
set<node>::iterator itr=split(r+1),itl=split(l);
vp.clear();
for(;itl!=itr;itl++)vp.push_back(pair<ll,int>(itl->v,itl->r-itl->l+1));
sort(vp.begin(),vp.end());
for(vector<pair<ll,int> >::iterator it=vp.begin();it!=vp.end();it++)
{
k=k-it->second;
if(k<=0)return it->first;
}
return (ll)-1;
}
ll sum(int l,int r,int ex,int mod)
{
set<node>::iterator itr=split(r+1),itl=split(l);
ll res=0;
for(;itl!=itr;itl++)res=(res+(ll)(itl->r-itl->l+1)*qpow(itl->v,ll(ex),ll(mod)))%mod;
return res;
}
};
ODT odt;
树状数组
ll tree[maxn][maxm]; //二维树状数组
int lowbit(int i) //最低位运算
{
return i&(-i);
}
void add(int len,int i,ll v) //对第len维进行添加
{
while(i<=n)
{
tree[len][i]=(tree[len][i]+v)%mod; //添加向上添加
i=i+lowbit(i);
}
}
ll sum(int len,int x) //对第len维进行求和
{
ll s=0;
while(x)
{
s=s+tree[len][x];
x=x-lowbit(x); //求和向下求和
}
return s;
}
树状数组中的规律
1.给定x,如何如何求x的最低位1?
ll lowbit(ll x) //最低位运算
{
return x&(-x);
}
这非常容易办到,lowbit(x)也表示了x这个数最低位组成的值。
2.给定[l,r],如何求取.?
利用前缀和知识,等价于
。
我们观察lowbit的性质,我们可以发现lowbit(i)从数学角度统计,其值等于能被最大的2的幂整除的值。也即是i%1==0,i%2==0,....,i%(2^m)==0,i%(2^(m+1))!=0,则lowbit(i)=2^m。故x中有多少个能被2^m整除的数,即有多少个lowbit(i)大于等于2^m(也可以看成一种和),故lowbit(i)=2^m的个数为x/(2^m)-x/(x^(m+1))也即是(x>>m)-(x>>(m+1))个lowbit(i)=2^m的数。
故等于((x>>m)-(x>>(m+1)) << m),枚举m即可:
ll sum(ll x)
{
ll ans=0;
for(int i=0;(x>>i);i++) ans=ans+(((x>>i)-(x>>i+1))<<i);
return ans;
}
3.数x,在长度为n的树状数组中被多少个数覆盖?
这个问题仔细思考一下就转化成了如果我们添加一个数到第x位,则哪些位置会被添加该数,也即是树状数组的add函数。
void add(int x,ll v)
{
while(i<=n)
{
tree[i]=tree[i]+v; //添加向上添加
i=i+(i&(-i));
}
}
从中我们可以看出问题的答案就是add中while循环被执行的次数。
int times(int x)
{
int ans;
while(i<=n)
{
ans++;
i=i+(i&(-i));
}
return ans;
}
线段树和树状数组的时间优化:
线段树和树状数组的常数是比较大的,尤其是查询、修改的量大,并且区间较小。
使用线段树的时候如果题目是关于求和的,我们可以用树状数组的前缀和进行优化,这样可以节约大量数组访问的时间。
但是如果线段树在无法使用树状数组进行优化或者树状数组也要大量时间的时候,我们就要从具体的操作进行分析减少时间。
优化一:大量相似(相同)区间段查询
例如:二分加线段树
在二分的过程中,我们会发现其实当二分到某一端之后会在那一部分密集左右二分变化,这就意味着那一部分的区间操作大部分有相同区间,如果说我们记录上一次查询区间位置和获得值,在下一次查询时可以直接调用该部分区间以节约大量区间查询的时间,并在该基础上进行小幅度修改即可。