学线段树的原因是因为cf的一道题目始终想不出来怎么优化,后来知道区间查询和修改要用到线段树。。。
原题:Iva & Pav
区间最值查询:可以高效地找到给定区间内的最大值、最小值等。
区间和查询:可以高效地计算给定区间内元素的和、积等。
区间更新:可以高效地对给定区间内的元素进行更新操作,如增加一个固定值、赋值等。
区间覆盖:可以将给定区间内的元素全部赋值为一个固定值。
区间合并:可以将多个区间合并成一个区间,快速地进行区间合并操作。
区间离散化:可以将区间内的元素进行离散化处理,方便进行查询和统计操作。
区间交集:可以快速地找到多个区间之间的交集
刚学完树状数组来学线段树,一开始还不知道他们具体的差别在哪里,那么以下是我的理解。
1.树状数组是前缀和优化,要用到前缀和的时候较为方便。
2.树状数组用来进行单点修改,区间查询;或者区间修改,单点查询较为方便,而区间查询和区间修改较为复杂,因此可以用线段树优化。
3.线段树适用于需要频繁的区间查询和更新操作的问题,如区间最值、区间和等,能够灵活处理各种区间操作。
4.树状数组适用于一维数组的前缀和查询和更新操作,对于简单的区间操作也能够提供高效的解决方案。
直接看代码:
- #include
- #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<
"\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
- #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;
- }