P1816 忠诚

P1816 忠诚

题目描述

老管家是一个聪明能干的人。他为财主工作了整整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

当时看着标签是dp才做的,结果发现是一个数据结构体qwq

那就来复习一下数据结构吧

区间最值,很容易想到一下几种思路:

线段树,树状数组,st表

我自己的方法是线段树,AC代码如下(很简单的板子)

#include<bits/stdc++.h>
using namespace std;

int a[502000],t[502000];
int n,m;

void build(int p,int l,int r)
{
    if(l==r) 
    {
        t[p]=a[l];
        return;
    }
    
    int mid=(l+r)/2;
    build(2*p,l,mid);
    build(2*p+1,mid+1,r);
    t[p]=min(t[2*p],t[2*p+1]);
}

int find(int x,int y,int l,int r,int p)
{
    if(x<=l&&y>=r) 
    {
        return t[p];
    }
    
    int mid=(l+r)/2;
    int a=9999999,b=9999999;
    if(x<=mid) a=find(x,y,l,mid,2*p);
    if(y>mid) b=find(x,y,mid+1,r,2*p+1);
    return min(a,b);
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    
    build(1,1,n);
    
    while(m--)
    {
        int x,y;
        cin>>x>>y;
        cout<<find(x,y,1,n,1)<<" ";
    }
    
    return 0;
    
}

题解中的其他方法:

树状数组:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int a[1000001];    
int tree[1000001];   
int n,m;
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int v)
{
    while(x<=n)
    {
        if(tree[x]>v)
          tree[x]=v;
         else
          return;     
        x+=lowbit(x);  
    }
}
int getmin(int x,int y)
{
    int now=y;
    int maxl=2147483647;
    while(now>=x)
    {
        if(now-lowbit(now)>x)
        {
           maxl=min(maxl,tree[now]);
           now-=lowbit(now);
        }
        else
         {
             maxl=min(maxl,a[now]);
             --now;
         }
    }
    return maxl;
}
int main()
{
    memset(tree,127,sizeof(tree));
    cin>>n>>m;
    int x,y;
    //n++;
    for(int i=1;i<=n;++i)
     {
         cin>>a[i];
         update(i,a[i]);
     }
    for(;m>0;--m)
    {
        cin>>x>>y;
        cout<<getmin(x,y)<<' ';
    }
    return 0; 
}

st表:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int a[100005][20],m,n;
inline int getint()                    //读入优化
{
    int a=0;char x=getchar();bool f=0;
    while((x<'0'||x>'9')&&x!='-')x=getchar();
    if(x=='-')f=1,x=getchar();
    while(x>='0'&&x<='9')
    {a=a*10+x-'0';x=getchar();}
    return f?-a:a;
}
void rmq_st(int n)                //预处理,st表
{
    for(int j=1;j<=20;j++)
    for(int i=1;i<=n;i++)           //思考为什么外循环j套i,而不是外循环i套j
    if(i+(1<<j)-1<=n)
    a[i][j]=min(a[i][j-1],a[i+(1<<(j-1))][j-1]);   //原因在于每个区间更新都是由1个点开始慢慢增加区间范围
}
int main()
{
    m=getint();n=getint();
    for(int i=1;i<=m;i++)a[i][0]=getint();   //读入数据,对于每个点开始时最小值就是它本身
    rmq_st(m);
    while(n--)
    {
        int l=getint(),r=getint(),k=log2(r-l+1);
        printf("%d ",min(a[l][k],a[r-(1<<k)+1][k]));        //查询时注意思路 
    }
    return 0;
}

还有一种不知道是什么神奇方法的方法(看着像是一种贪心?)

#include<bits/stdc++.h>
using namespace std;
struct fff{
  int x,y;  
};
fff a[100001];
bool cmp(fff l,fff v){
    return l.x<v.x;
}
int main(){
    int m,n,t,k;
    scanf("%d%d",&m,&n);
        for(int i=1;i<=m;i++){
            scanf("%d",&a[i].x);
            a[i].y=i;    //为每个账目编号;
        }
        sort(a+1,a+1+m,cmp);
        for(int i=1;i<=n;i++){
            scanf("%d%d",&t,&k);
            for(int j=1;j<=m;j++){
                if(a[j].y>=t&&a[j].y<=k){
                    printf("%d ",a[j].x);
                    break;     //一个小优化;
                }
            }
        }
    return 0;
}
//主要是快排,先把账目排一遍,再找提问范围里靠前的,找到后就输出;(比较好理解)
原文地址:https://www.cnblogs.com/yxr001002/p/14116107.html