线段树(这是一个神奇的东西)

首先,让我们用一道简单的题来引入线段树:
输入n,m,在接下来的n行里输入一个数列a1,a2……an,
之后的m行输入三个数num,x,y其中num表示操作类型,
当num等于一时表示a[x]=y,等于二时表示输出x到y的最大值

很明显,用模拟就可以了,暴力上代码~~

#include<iostream>
using namespace std;
int n,m,a[1010];

void chuli(int x,int y)
{
    a[x]=y;
    return;
}

void work(int x,int y)
{
    int ans=1<<31; 
    for(int i=x;i<=y;++i)
        ans=max(ans,a[i]);
    cout<<ans<<endl;
    return;
}

int main()
{
    freopen("test.in","r",stdin);
    freopen("test1.out","w",stdout);
    cin>>n>>m;
    for(int i=1;i<=n;++i)
        cin>>a[i];
    for(int i=1;i<=m;++i){
        int num,x,y;
        cin>>num>>x>>y;
        if(num==1)
            chuli(x,y);
        else
            work(x,y);
    }
    return 0;
}

时间法度为o(n*m),如果n为一个较大的值的时候,就会超时。

接下来,介绍一个o(n*logn)的做法,我们可以将这个数组分段,并且求出每一段的最大值,如果是操作一,那么就将x所在的那一段重新求一个最大值。如果是操作二,那么求在x和y之间的分段的最大值的最大值,在求x和y所在的分段的最大值,他们的最大值就是x到y的最大值
附上代码:

#include<iostream>
#include<cmath>
using namespace std;
int n,m,a[100100],f[400];
int p;

void work(int x,int y)
{
    int l=(x-1)/p+1;
    int r=(y-1)/p+1;
    int ans=1<<31;
    if(l==r){
        for(int i=x;i<=y;++i){
            ans=max(ans,a[i]);
        }
    }
    else{
        for(int i=l+1;i<=r-1;++i)
            ans=max(ans,f[i]);
        for(int i=x;i<=l*p;++i)
            ans=max(ans,a[i]);
        for(int i=(r-1)*p+1;i<=y;++i)
            ans=max(ans,a[i]);
    }
    cout<<ans<<endl;
    return;
}

void operate(int x,int y)
{
    a[x]=y;
    int l=(x-1)/p+1;
    f[l]=1<<31;
    for(int i=(l-1)*p+1;i<=l*p;++i)
        f[l]=max(f[l],a[i]);
}

int main()
{
    freopen("test.in","r",stdin);
    freopen("test2.out","w",stdout);
    cin>>n>>m;
    p=sqrt(n);
    for(int i=1;i<=p+1;++i)
        f[i]=(1<<31);
    for(int i=1;i<=n;++i){
        cin>>a[i];
        f[(i-1)/p+1]=max(f[(i-1)/p+1],a[i]);
    }
    for(int i=1;i<=m;++i){
        int num,x,y;
        cin>>num>>x>>y;
        if(num==1){
            operate(x,y);
        }
        if(num==2)
            work(x,y);
    }
    return 0;
}

但是,如果n的值是500000时还是会超时,所以,现在就要引出线段树了,刚开始是表示区间1到n之间的最大值,将这个区间从中间分开,分别求两边的最大值,直到区间中只有一个元素为止(即左右边界都是同一个数)
接下来就是操作了,如果是第一种操作,就将那个元素改值,然后再求最大值,然后往下面求最大值。有几种情况在程序中的check函数里已经十分清楚地表现了出来。

void change(int x,int l,int r,int p,int value)
{
    if(l==r){
        tree[x]=value;
        return;
    }
    int m=(l+r)/2;
    if(p<=m)
        change(x+x,l,m,p,value);
    else
        change(x+x+1,m+1,r,p,value);
    tree[x]=max(tree[x+x],tree[x+x+1]);
}

如果是第二种操作的话,就直接输出就好了(这个东西原本就是求一个区间的最大值的。。。)
最后附上代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<utility>
using namespace std;
const int maxN=100010;
int n,m,a[maxN],tree[maxN<<2];

void maketree(int x,int l,int r)
{
    if(l==r){
        tree[x]=a[l];
        return;
    }
    int m=(l+r)/2;
    maketree(x+x,l,m);
    maketree(x+x+1,m+1,r);
    tree[x]=max(tree[x+x],tree[x+x+1]);
}

void change(int x,int l,int r,int p,int value)
{
    if(l==r){
        tree[x]=value;
        return;
    }
    int m=(l+r)/2;
    if(p<=m)
        change(x+x,l,m,p,value);
    else
        change(x+x+1,m+1,r,p,value);
    tree[x]=max(tree[x+x],tree[x+x+1]);
}

int query(int x,int l,int r,int left,int right)
{
    if(l==left&&r==right)
        return tree[x];
    int m=(l+r)/2;
    if(right<=m)
        return query(x+x,l,m,left,right);
    else if(left>m){
        return query(x+x+1,m+1,r,left,right);
    }
    else{
        return max(query(x+x,l,m,left,m),query(x+x+1,m+1,r,m+1,right));
    }
}

int main()
{
    freopen("test.in","r",stdin);
    freopen("test3.out","w",stdout);
    cin>>n>>m;
    for(int i=1;i<=n;++i)
        cin>>a[i];
    maketree(1,1,n);//O(n)
    for(int i=1;i<=m;++i){
        int num,x,y;
        cin>>num>>x>>y;
        if(num==1){
            change(1,1,n,x,y);
        }
        else{
            cout<<query(1,1,n,x,y)<<endl;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/ljp946/article/details/81295419
今日推荐