线段树

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
");
            continue;
            }
        printf("%d
",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
    ",find(1,max(1,i-m),i-1));
    51  } 
为何要逼自己长大,去闯不该闯的荒唐
原文地址:https://www.cnblogs.com/zjzjzj/p/11129154.html