线段树题胡乱整理

一.洛谷 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\n");
    for(int i=2; i<=n; i++)
    {
        ans=0x7fffffff;
        b=i-1; 
        if(i>m) 
        {
            a=i-m;                         //左端点 
            ask(1);
            printf("%d\n",ans);
        }
        else if(i<=m)
        {
            a=1;
            ask(1);
            printf("%d\n",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\n");
    q[1]=1;
    for(int i=2; i<=n; ++i)
    {
        printf("%d\n",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

 

posted @ 2017-05-17 21:15  夜雨声不烦  阅读(162)  评论(0编辑  收藏  举报