• 线段树题胡乱整理


    一.洛谷 P1816 忠诚

    ----------------------------------T1----------------------------------

    题目描述

    老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。

    输入输出格式

    输入格式:

    输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。

    第二行为m个数,分别是账目的钱数

    后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。

    输出格式:

    输出文件中为每个问题的答案。具体查看样例。

    输入输出样例

    输入样例#1:
    10 3
    1 2 3 4 5 6 7 8 9 10
    2 7
    3 9
    1 10
    输出样例#1:
    2 3 1

    思路:

      1)线段树查询区间最小值
      2)RMQ

    代码酱=w=

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    
    using namespace std;
    
    int n,m;
    int x,y;
    int a[100001];//保存数
    int f[100001][21];//f[i][j]表示第i个数向后  2的j次方  个数取得的最小值 
    
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=n;++i) cin>>a[i];
        for(int i=1;i<=n;++i) f[i][0]=a[i];
        for(int j=1;j<=20;++j)
        {
            for(int i=1;i<=n;++i)
            {
                if(i+(1<<j)-1<=n)
                {
                    f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);//RMQ初始化 
                }
            }
        }
        int i,j;
        for(int x=1;x<=m;++x)
        {
            cin>>i>>j;
            int k=log(j-i+1)/log(2);
            cout<<min(f[i][k],f[j-(1<<k)+1][k])<<' ';
        }
        return 0;
    }
    RMQ
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #define INF 0x7fffffff
    using namespace std;
    
    const int N = 1000010;
    int n,m,x,y,s[N],q,log[N];
    int f[N][20],ans[N];
    
    int main()
    {
        cin>>n>>m;
        for(int i=1; i<=n; i++) cin>>s[i];
        //log2 a = x,表示2的x次方=a
        for(int i=2; i<=n; i++) log[i]=log[i>>1]+1; //log数组的下标表示 a,log数组中存的是2的多少次方
        for(int i=1; i<=n; i++)
            for(int j=1; j<=20; j++)
                f[i][j]=INF;
        for(int i=1; i<=n; i++) f[i][0]=s[i];
        for(int i=1,k=1; i<=log[n]; i++,k*=2) //k为2的i-1次方
            for(int j=1; j+k-1<=n; j++) //j+k-1为左半端的最后一个
                f[j][i]=min(f[j][i-1],f[j+k][i-1]);//i-1次方是一半
        for(int i=1; i<=m; i++)
        {
            cin>>x>>y;
            int len=log[y-x+1];
            ans[i]=min(f[x][len],f[y-(1<<len)+1][len]); //1<<len 表示2的len次方
        }
        for(int i=1; i<=m; i++)
            cout<<ans[i]<<" ";
        return 0;
    }
    RMQ2
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long LL;
    
    const int M = 1e5;
    int n,m,a,b;
    LL ans;
    LL aa[M];
    
    struct A{
        LL l,r;
        LL w;
    }tree[M*4+1];
    
    int min(int a,int b)
    {return a < b ? a : b;}                            //重新定义min函数
    
    inline void build(int k,int ld,int rd)
    {
        tree[k].l=ld,tree[k].r=rd;
        if(ld == rd)
        {
            scanf("%d",&tree[k].w);                        //注意数据! 
            return ; 
        }
        int m = (tree[k].l+tree[k].r) >> 1;
        build(k*2,ld,m);
        build(k*2+1,m+1,rd);
        tree[k].w=min(tree[k*2].w,tree[k*2+1].w);     //具题意来 
    }
    
    inline void ask(int k)
    {
        if(tree[k].l>=a && tree[k].r<=b)
        {
            ans=min(ans,tree[k].w);
            return ;
        }
        int m = (tree[k].l+tree[k].r) >> 1;
        if(a <= m)  ask(k*2);
        if(b > m) ask(k*2+1);
    }
    
    int main()
    {
        scanf("%d%d",&m,&n);
        build(1,1,m);                                //建树 
    
        for(int i=1;i<=n;i++)                        //n次询问 
        {
            ans = 0x7fffffff;
            scanf("%d%d",&a,&b);                    //区间左右端点 
            ask(1);
            printf("%lld ",ans);
        }
    
        return 0;
    }
    线段树

    二.P1440 求m区间内的最小值

    ----------------------------------T2----------------------------------

    题目描述

    一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。

    输入输出格式

    输入格式:

    第一行两个数n,m。

    第二行,n个正整数,为所给定的数列。

    输出格式:

    n行,第i行的一个数ai,为所求序列中第i个数前m个数的最小值。

    输入输出样例

    输入样例#1:
    6 2
    7 8 1 4 3 2
    
    输出样例#1:
    0
    7
    7
    1
    1
    3 
    

    说明

    【数据规模】

    m≤n≤2000000

    代码酱=u=

    未完....TLE2个点....

    #include <cstdio>
    #include <iostream>
    
    using namespace std;
    
    const int M = 2e6 ;
    int n,m,a,b,ans;
    
    struct Segment_Tree{
        int l,r;    //左右端点 
        int w;         //该点的值 
    }tree[M*4+1];    //需要开4倍空间,具体原因不明 
    
    int min(int a,int b)
    {return a < b ? a : b;}                     //重新定义min函数
    
    //最小
    inline void build(int k,int ld,int rd)
    {
        tree[k].l=ld,tree[k].r=rd;
        if(tree[k].l == tree[k].r)
        {
            scanf("%d",&tree[k].w);
            return ;
        }
        int m = (ld + rd) >> 1;
        build(k*2,ld,m);
        build(k*2+1,m+1,rd);
        tree[k].w=min(tree[k*2].w,tree[k*2+1].w);
    }
    
    //最小 
    inline void ask(int k)
    {
        if(tree[k].l >= a && tree[k].r <= b) //表示当前在查询的区间之内 
        {
            ans=min(ans,tree[k].w);             //找最小值更新答案(与最小值查询相比仅仅修改了这里) 
            return ;
        }
        int m = (tree[k].l+tree[k].r) >> 1;
        if(a <= m)  ask(k*2); 
        if(b > m) ask(k*2+1);
    }
    
    int main()
    {
    /*求出每一项前的m个数到它这个区间内的最小值.若前面的数不足m项则从第1个数开始.若前面没有数则输出0*/
        scanf("%d%d",&n,&m);                 //输入n个结点
        build(1,1,n);                         //建树
        printf("0
    ");
        for(int i=2; i<=n; i++)
        {
            ans=0x7fffffff;
            b=i-1; 
            if(i>m) 
            {
                a=i-m;                         //左端点 
                ask(1);
                printf("%d
    ",ans);
            }
            else if(i<=m)
            {
                a=1;
                ask(1);
                printf("%d
    ",ans);
            }
        }
        return 0;
    }
    线段树

    RMQ做法:(会各种MLE,TLE,还是不要用了....)

    正解:单调队列

    #include<iostream>
    #include<cstdio>
    #include<queue>
    using namespace std;
    
    const int M = 2000010;
    int q[M];
    int n,m,x,ans,a[M];
    int head=1,tail=1;
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
        printf("0
    ");
        q[1]=1;
        for(int i=2; i<=n; ++i)
        {
            printf("%d
    ",a[q[head]]);
            if(q[head]<=i-m)
                head++;
            while(a[i]<=a[q[tail]] && head<=tail)
                tail--;
            q[++tail]=i;
        }
        return 0;
    }
    AC

    三.P1531 I Hate It

    ----------------------------------T3----------------------------------

    题目背景

    很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。这让很多学生很反感。

    题目描述

    不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩

    输入输出格式

    输入格式:

    第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。学生ID编号分别从1编到N。第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。当C为'U'的时候,表示这是一条更新操作,如果当前A学生的成绩低于B,则把ID为A的学生的成绩更改为B,否则不改动。

    输出格式:

    对于每一次询问操作,在一行里面输出最高成绩

    输入输出样例

    输入样例#1:
    5 6
    1 2 3 4 5
    Q 1 5
    U 3 6
    Q 3 4
    Q 4 5
    U 2 9
    Q 1 5
    输出样例#1:
    5
    6
    5
    9

    思路:

      裸的线段树

    坑点:

      1)数组记得开得大一点

      2)还有就是在进行更改的时候要跟当前的那个值进行比较,然后再进行修改

    上代码=u=:

    #include <iostream>
    #define LL long long
    #define lson (l+r)<<1
    #define rson (l+r)<<1|1
    using namespace std;
    
    const int N = 2e5;
    int n,m,a,b,ans;
    int sca[N];
    
    struct V {
        int l,r;
        int w;
    }t[N*4+10086];
    
    int max(int a,int b)
    {return a > b ? a : b;}
    
    inline void build(int k,int l,int r)
    {
        t[k].l=l,t[k].r=r;
        if(l==r)
        {
            t[k].w=sca[l];
            //t[k].w=sca[r];
            return;
        }
        int m=(l+r)>>1;
        build(lson,l,m);
        build(rson,m+1,r);
        t[k].w=max(t[lson].w,t[rson].w);
    }
    
    inline void ask(int k,int x,int y)
    {
        int l=t[k].l,r=t[k].r;
        if(l==x && r==y)
        {
            ans=max(t[k].w,ans);
            return;
        }
        int m=(l+r)>>1;
        if(y<=m)
            ask(lson,x,y);
        if(x>m)
            ask(rson,x,y);
        if(m<y && m >=x)
            ask(lson,x,m),ask(rson,m+1,y);
    }
    
    inline void changes(int k)
    {
        int l=t[k].l,r=t[k].r;
        if(l==r)
        {
            t[k].w=max(t[k].w,b);
            return;
        }
        int m=(l+r)>>1;
        if(a<=m) changes(lson);
            else changes(rson);
        t[k].w=max(t[lson].w,t[rson].w);
    }
    
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
            cin>>sca[i];
        build(1,1,n);
        char ch;
        for(int i=1;i<=m;i++)
        {
            cin>>ch>>a>>b;
            if(ch=='Q')
            {
                ask(1,a,b);
                cout<<ans<<endl;
                ans=0;
            }
            else
                changes(1);
        }
        return 0;
    }
    View Code

     

    如果运气好也是错,那我倒愿意错上加错!

    ❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀

  • 相关阅读:
    远程桌面连接偶尔无法连接的解决方案
    事物复制遇到的几个错误
    几条关于查看和删除发布和分发的命令
    Winform- TreeView的使用例子
    Winform- 界面开发之布局控件"WeifenLuo.WinFormsUI.Docking"的使用
    Winform- IrisSkin.dll轻松实现窗体换肤功能
    Oracle- 备份单表结构和单表数据
    MSSQLSERVER数据库- 作业调度定时备份数据库
    Oracle- plsql developer如何查询SQL语句执行历史记录
    MSSQLSERVER数据库- SQL删除重复数据的五种方式
  • 原文地址:https://www.cnblogs.com/zxqxwnngztxx/p/6869568.html
Copyright © 2020-2023  润新知