1. 程式人生 > >洛谷——RMQ

洛谷——RMQ

amp 輸出格式 clu 每天 n) space printf while turn

1.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
(*^__^*) 嘻嘻…… 代碼:

1.AC代碼——線段樹做法
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

const int N = 2000003;
int m,n,a,b,ans;
struct Tree{ int l;int r;int w; }t[4*N]; void build(int k,int ll,int rr) { t[k].l=ll,t[k].r=rr; if(ll == rr) { scanf("%d",&t[k].w); return ; } int mid=(ll+rr)/2; build(k*2,ll,mid); build(k*2+1,mid+1,rr); t[k].w=min(t[k*2].w,t[k*2+1].w); }
void ask(int k) { if(t[k].l>=a&&t[k].r<=b) { ans=min(ans,t[k].w); return ; } int mid=(t[k].l+t[k].r)/2; if(a<=mid) ask(k*2); if(b>mid) ask(k*2+1); } int main() { scanf("%d%d",&n,&m); build(1,1,n); for(int i=1; i<=n; i++) { a=i-m,b=i-1; if(i==1) { cout<<0<<endl; continue ; } if(a<1) a=1; ans=0x7fffffff; ask(1); printf("%d\n",ans); } return 0; }
2.RMQ做法(會T掉)
#include<iostream>
#include<cstdio>
using namespace std;

const int N = 10003;
int n,m,x,y,s[N],q,log[N];
int f[N][15],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<=n; j++) 
            f[i][j]=0x7fffffff;
    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;
}

2.P1440 求m區間內的最小值

題目描述

一個含有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

(*^__^*) 嘻嘻…… 代碼:

1.AC代碼——單調隊列

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;

int q[2000003];
int n,m,x,ans,a[2000003];
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;
}

2.線段樹做法—(會T兩個點)

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

const int N = 2000003;
int m,n,a,b,ans;
struct Tree{
    int l;int r;int w; 
}t[4*N];

void build(int k,int ll,int rr) {
    t[k].l=ll,t[k].r=rr;
    if(ll == rr) {
        scanf("%d",&t[k].w);
        return ;
    }
    int mid=(ll+rr)/2;
    build(k*2,ll,mid);
    build(k*2+1,mid+1,rr);
    t[k].w=min(t[k*2].w,t[k*2+1].w);
}

void ask(int k) {
    if(t[k].l>=a&&t[k].r<=b) {
        ans=min(ans,t[k].w);
        return ;
    }
    int mid=(t[k].l+t[k].r)/2;
    if(a<=mid) ask(k*2);
    if(b>mid) ask(k*2+1);
}

int main() {
    scanf("%d%d",&n,&m);
    build(1,1,n);
    for(int i=1; i<=n; i++) {
        a=i-m,b=i-1;
        if(i==1) {
            cout<<0<<endl;
            continue ;
        }
        if(a<1) a=1;
        ans=0x7fffffff;
        ask(1);
        printf("%d\n",ans);
    }
    return 0;
}

自己選的路,跪著也要走完!!!

洛谷——RMQ