线段树

1、建树

a、对于二分到的每一个结点,给它的左右端点确定范围。
b、如果是叶子节点,存储要维护的信息,再回到父节点时累计到父节点去。
c、合并

代码

void build(int k,int t,int w) { int mid; if (t>w) return; if (t==w) { tree[k].l=t;tree[k].r=w; tree[k].w=a[t]; return; } mid=(t+w)/2; build(k*2,t,mid); build(k*2+1,mid+1,w); tree[k].l=t;tree[k].r=w; tree[k].w=tree[k*2].w+tree[k*2+1].w; }

单点查询

查询一个点的状态,设待查询点为x
a、如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。
b、如果不是,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid。
c、如果x<=mid,则递归它的左孩子,否则递归它的右孩子

代码

void ask(int k) { if(tree[k].l==tree[k].r) //当前结点的左右端点相等,是叶子节点,是最终答案 { ans=tree[k].w; return ; } int m=(tree[k].l+tree[k].r)/2; if(x<=m) ask(k*2);//目标位置比中点靠左,就递归左孩子 else ask(k*2+1);//反之,递归右孩子 }

单点修改

即更改某一个点的状态。
结合单点查询的原理,找到x的位置;根据建树状态合并的原理,修改每个结点的状态。
void add(int k) { if(tree[k].l==tree[k].r)//找到目标位置 { tree[k].w+=y; return; } int m=(tree[k].l+tree[k].r)/2; if(x<=m) add(k*2); else add(k*2+1); tree[k].w=tree[k*2].w+tree[k*2+1].w;//所有包含结点k的结点状态更新 }

区间查询

代码

void sum(int k) { if(tree[k].l>=x&&tree[k].r<=y) { ans+=tree[k].w; return; } int m=(tree[k].l+tree[k].r)/2; if(x<=m) sum(k*2); if(y>m) sum(k*2+1); }

区间修改

同理

我们不要递归到每个节点。所以要有一个新的概念:懒标记。

就像新年的时候的压岁钱,只有要用的时候才用,不要的直接给父母保管。

所以,传下来的更改值若在一个区间里,就不再下传,修改完该节点信息后,在此节的懒标记上打一个更改值。

当需要递归这个节点的子节点时,标记下传给子节点。这里不必管用哪个子节点,两个都传下去。

①当前节点的懒标记累积到子节点的懒标记中。

②修改子节点状态。在引例中,就是原状态+子节点区间点的个数父节点传下来的懒标记。

③父节点懒标记清0。这个懒标记已经传下去了,欠债还清,不用再还了。

下传代码

void pushdown(int k) { tree[k*2].w+=((tree[k*2].r-tree[k*2].l+1)*tree[k].f); tree[k*2+1].w+=((tree[k*2+1].r-tree[k*2+1].l+1)*tree[k].f); tree[k*2].f+=tree[k].f; tree[k*2+1].f+=tree[k].f; tree[k].f=0; }

区间修改代码

void add(int k,int t,int w) { int mid; if (t>w) return; if (x<=t&&w<=y) { tree[k].w+=((w-t+1)*z); tree[k].f+=z; return ; } mid=(t+w)/2; if (tree[k].f) pushdown(k); if (x<=mid) add(k*2,t,mid); if (y>mid) add(k*2+1,mid+1,w); tree[k].w=tree[k*2].w+tree[k*2+1].w; }

单点查询代码

 void ask(int k)//单点查询 { if(tree[k].l==tree[k].r) { ans=tree[k].w; return ; } if(tree[k].f) pushdown(k);//懒标记下传,唯一需要更改的地方 int m=(tree[k].l+tree[k].r)/2; if(x<=m) ask(k*2); else ask(k*2+1); }

区间查询代码

int ask(int k,int t,int w) { int mid; if (t>w) return 0; if (x<=t&&w<=y) { return tree[k].w; } mid=(t+w)/2; if (tree[k].f) pushdown(k); int sum=0; if (x<=mid)sum+=ask(k*2,t,mid); if (y>mid)sum+=ask(k*2+1,mid+1,w); tree[k].w=tree[k*2].w+tree[k*2+1].w; return sum; }

更具体的描述 无耻地推荐一下我的博客

所以说此题为线段树的拓展:把求区间和改为了区间最小值,所以只需要将懒标记存储的内容改为最小值,区间修改再加工一下。

部分代码如下:

void build(int k,int t,int w) { int mid; if (t>w) return; if (t==w) { tree[k].l=t;tree[k].r=w; tree[k].w=a[t]; return; } mid=(t+w)/2; build(k*2,t,mid); build(k*2+1,mid+1,w); tree[k].l=t;tree[k].r=w; tree[k].w=min(tree[k*2].w,tree[k*2+1].w); } int ask(int k,int t,int w) { int mid; if (t>w) return 0; if (x<=t&&w<=y) { return tree[k].w; } mid=(t+w)/2; int sum=INT_MAX; if (x<=mid)sum=min(sum,ask(k*2,t,mid)); if (y>mid)sum=min(sum,ask(k*2+1,mid+1,w)); return sum; } int read(int &x) { char c=getchar();int f=1; x=0; while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); } while (c>='0'&&c<='9') { x=x*10+(int)c-48; c=getchar(); } return x*f; } 

时间复杂度O(n log n)O(nlogn)

只要不卡常,速度和单调队列和RMQRMQ相差无几,也是一种求最值的方案,可以了解一下。

完整代码:

#include<bits/stdc++.h>
#define N 2000010 using namespace std; struct node{ int l,r,w,f; }tree[N*2+1]; int x,y,z,i,a[N],n,m; void build(int k,int t,int w) { int mid; if (t>w) return; if (t==w) { tree[k].l=t;tree[k].r=w; tree[k].w=a[t]; return; } mid=(t+w)/2; build(k*2,t,mid); build(k*2+1,mid+1,w); tree[k].l=t;tree[k].r=w; tree[k].w=min(tree[k*2].w,tree[k*2+1].w); } int ask(int k,int t,int w) { int mid; if (t>w) return 0; if (x<=t&&w<=y) { return tree[k].w; } mid=(t+w)/2; int sum=INT_MAX; if (x<=mid)sum=min(sum,ask(k*2,t,mid)); if (y>mid)sum=min(sum,ask(k*2+1,mid+1,w)); return sum; } int read(int &x) { char c=getchar();int f=1; x=0; while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); } while (c>='0'&&c<='9') { x=x*10+(int)c-48; c=getchar(); } return x*f; } signed main() { //freopen(".in","r",stdin); //freopen(".out","w",stdout); n=read(n);m=read(m); memset(a,INT_MAX,sizeof(a)); for (int i=1;i<=n;i++) a[i]=read(a[i]); build(1,1,n); for (int i=1;i<=n;i++) { int c; x=i-m;y=i-1; if (x<=0) x=1; if (x>y) { printf("0\n"); continue; } printf("%d\n",ask(1,1,n)); } }
  • 线段树求区间最小
  •  1 #include<iostream>
     2 #include<cmath>
     3 #include<cstdio>
     4 using namespace std;
     5 struct sb
     6 {
     7     int l,r,minn;
     8 }t[20000010*2];
     9 int read(int &x)
    10 {
    11     char c=getchar();int f=1;
    12     x=0;
    13     while (c<'0'||c>'9')
    14       {
    15       if (c=='-') f=-1;
    16       c=getchar();
    17       }
    18     while (c>='0'&&c<='9')
    19       {
    20         x=x*10+(int)c-48;
    21         c=getchar();
    22       }
    23     return x*f;
    24 }
    25 void build(int k,int a,int b)
    26 {
    27     t[k].l=a; t[k].r=b;
    28     if(a==b) { t[k].minn=read(t[k].minn); return; }    
    29     int mid=(a+b)>>1;
    30     build(k<<1,a,mid);
    31     build(k<<1|1,mid+1,b);
    32     t[k].minn=min(t[k<<1|1].minn,t[k<<1].minn);
    33 }
    34 int find(int k,int a,int b)
    35 {
    36     if (t[k].l==a&&t[k].r==b) return t[k].minn;
    37     int mid=(t[k].l+t[k].r)>>1;
    38     if (b<=mid) return find(k<<1,a,b);
    39     else if (a>mid) return find(k<<1|1,a,b);
    40     else
    41       return min(find(k<<1,a,mid),find(k<<1|1,mid+1,b)); 
    42 }
    43 int main ()
    44 {
    45     int n,m;
    46     cin>>n>>m;
    47     build(1,1,n);
    48     cout<<"0"<<endl;
    49     for (int i=2;i<=n;i++)
    50         printf("%d\n",find(1,max(1,i-m),i-1));
    51  } 

     

转载于:https://www.cnblogs.com/zjzjzj/p/11129154.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值