RMQ入門——[kuangbin]RMQ練習

RMQ區間最值查詢,這類問題一般有多種方式可以解決,我使用的是ST稀疏表的形式,利用倍增的思想進行預處理然後實現O(1)的效率查詢。

A - Balanced Lineup POJ - 3264

就是查詢區間內的最大值和最小值的差,建兩個st表就行了,這裏我使用的遞歸方式建立的st表。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<map>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 105;
const int maxn = 50005;
const int M = 25;
struct node{
    int l, r, minn,maxx;
}treenode[maxn*3];
int A[maxn];
int temp;
int maxxx,minnn;

void build(int i, int l, int r){
    treenode[i].minn = inf; treenode[i].maxx = -inf;
    treenode[i].l = l; treenode[i].r = r;
    if (l == r){
        treenode[i].maxx=treenode[i].minn = A[l];
        return;
    }
    int m = (l + r) >> 1;
    build(i << 1, l, m);
    build(i << 1 | 1, m+1, r);
    treenode[i].maxx = max(treenode[i << 1].maxx, treenode[i<<1|1].maxx);
    treenode[i].minn = min(treenode[i << 1].minn, treenode[i<<1|1].minn);
}

void query(int i, int l, int r){
    int lll = treenode[i].l, rrr = treenode[i].r;
    if (r<lll || l>rrr){ return; }
    if (l <= lll&&r >= rrr){
        maxxx = max(maxxx, treenode[i].maxx);
        minnn = min(minnn, treenode[i].minn);
        return;
    }
    int m = (lll + rrr) >> 1;
    if(l<=m)query(i << 1, l, r);
    if(r> m)query(i << 1 | 1, l, r);
}
int n, q;
int main() {
    int x, y;
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++){ scanf("%d", &A[i]); }
    build(1, 1, n);
    for (int i = 0; i < q; i++){
        scanf("%d%d", &x, &y);
        maxxx = -inf, minnn = inf;
        query(1, x, y);
        printf("%d\n",maxxx-minnn );
    }
    return 0;
}

B - Frequent values POJ - 3368

單調不下降(數據連續)求區間內出現最頻繁的方式,ST表在處理區間最大最小值很有優勢,這題中,如果衆數出現的位置在ST表兩段連續區間的分割處的話,使得該衆數在這兩段中都不是最多出現的,導致倍增表錯誤,因此在構造ST表時需要從中間向兩端統計中間的數值出現的次數,與兩個子表的值比較,查詢時也需要這樣做。

//計算lll的正確方式 lll=log(double(lll))/log(2.0)
#include<string.h>
#include<cstdio>
#include<queue>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=100005;
const int maxm=3000005;
const int inf=0x3f3f3f3f;
int  n,q;

int st[maxn][20];
int num[maxn];

void init(){
    for(int i=0;i<n;i++)st[i][0]=1;
    for(int j=1;(1<<j)<=n;j++){
        for(int i=0;i+(1<<j)-1<n;i++){
            int rig=i+(1<<j)-1;
            st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
            int cc=num[i+(1<<(j-1))];
            int x=i+(1<<(j-1)),y=x;
            while(x-1>=i&&num[x-1]==cc)x--;
            while(y+1<=rig&&num[y+1]==cc)y++;
            st[i][j]=max(st[i][j],y-x+1);
        }
    }
}

int rmq(int lef,int rig){
    int lll=rig-lef+1;
    lll=log(double(lll))/log(2.0);
    int ans=max(st[lef][lll],st[rig-(1<<lll)+1][lll]);
    int cc=num[rig-(1<<lll)+1];
    int x=rig-(1<<lll)+1,y=x;
    while(x-1>=lef&&num[x-1]==cc)x--;
    while(y+1<=rig&&num[y+1]==cc)y++;
    int res=y-x+1;
    ans=max(res,ans);
    return ans;
}

int main()
{
    while(scanf("%d",&n),n){
        scanf("%d",&q);
        for(int i=0;i<n;i++){
            scanf("%d",&num[i]);
        }
        init();
        int a,b;
        for(int i=0;i<q;i++){
            scanf("%d%d",&a,&b);
            printf("%d\n",rmq(a-1,b-1));
        }
    }
    return 0;
}

C - 降雨量 HYSBZ - 1067

中文題,處理回答比較麻煩

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<map>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
//const int mod = 1000000007;
const int maxm = 1000010;
const int maxn = 50005;
int n, c;
int d[maxn][30];
int a[maxn];
int year[maxn];
void rmq_init(){
    for (int i = 0; i <n; i++){ d[i][0] = a[i]; }
    for (int j = 1; (1 << j) <= n; j++){
        for (int i = 0; i + (1 << j) - 1 <n; i++){
            d[i][j] = max(d[i][j - 1], d[i + (1 << (j - 1))][j - 1]);
        }
    }
}

int rmq(int l, int r){
    if (l > r)return 0;
    int k = 0;
    while ((1 << (k + 1)) <= r - l + 1)k++;
    return max(d[l][k], d[r - (1 << k) + 1][k]);
}

int main() {
    int x, y;
    scanf("%d", &n);
    for (int i = 0; i < n; i++){
        scanf("%d%d", &year[i], &a[i]);
    }
    rmq_init();
    scanf("%d", &c);
    for (int i = 0; i < c; i++){
        scanf("%d%d", &x, &y);
        if (x>y){ printf("false\n"); continue; }
        if (x == y){ printf("true\n"); continue; }
        int lef = lower_bound(year, year + n, x) - year;
        int l = year[lef];
        int rig = lower_bound(year, year + n, y) - year;
        int r = year[rig];
        if (l != x&&y != r){ printf("maybe\n"); }
        else if (l==x&&r != y){
            int cc = rmq(lef + 1, rig - 1);
            if (cc < a[lef]){ printf("maybe\n"); }
            else{ printf("false\n"); }
        }
        else if(l!=x&&r==y){
            int cc = rmq(lef, rig - 1);
            if (a[rig]>cc){ printf("maybe\n"); }
            else{ printf("false\n"); }
        }
        else{
            if (a[rig] > a[lef]){ printf("false\n"); }
            else{
                int cc = rmq(lef + 1, rig - 1);
                if (cc < a[rig]){
                    if (rig - lef == r - l){ printf("true\n"); }
                    else{ printf("maybe\n"); }
                }
                else{ printf("false\n"); }
            }
        }
    }
    return 0;
}

To the Max POJ - 1050

找最大子矩陣,我是用的DP完成的,前綴和處理1到i行每列之和,然後枚舉起始行和終止行,轉化成一維DP就最大值,複雜度爲O(n^3)

//不知道跟rmq有什麼關係

#include<string.h>
#include<cstdio>
#include<queue>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=100005;
const int maxm=1005;
const int inf=0x3f3f3f3f;
int  n,m;

int matrix[105][105];
int qian[105][105];
int b[105];
int dp[105];

int main()
{
    while(~scanf("%d",&n)){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                scanf("%d",&matrix[i][j]);
            }
        }
        for(int j=0;j<n;j++){qian[0][j]=matrix[0][j];}
        for(int i=1;i<n;i++){
            for(int j=0;j<n;j++){
                qian[i][j]=qian[i-1][j]+matrix[i][j];
            }
        }
        int ans=-inf;
        memset(b,0,sizeof(b));
        for(int i=0;i<n;i++){
            for(int k=i;k<n;k++){
                for(int j=0;j<n;j++){
                    if(i)
                    b[j]=qian[k][j]-qian[i-1][j];
                    else
                    b[j]=qian[k][j];
                }

            memset(dp,-inf,sizeof(dp));
            dp[0]=b[0];
            ans=max(ans,dp[0]);
            for(int j=1;j<n;j++){
                dp[j]=max(b[j],b[j]+dp[j-1]);
                ans=max(ans,dp[j]);
            }
        }
        }
        printf("%d\n",ans);

    }
}

E - Cornfields POJ - 2019

二維矩陣處理區間最大值和最小值之差,也是打兩張表就行,因爲矩陣和查詢區間都是方陣,所以ST表只需要3個維度即可。

//因爲矩陣爲方形,st表只需要3個維度就行了
#include<string.h>
#include<cstdio>
#include<queue>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=255;
const int maxm=1005;
const int inf=0x3f3f3f3f;
int  n,b,k;
int num[maxn][maxn];
int stmax[maxn][maxn][10];
int stmin[maxn][maxn][10];
void rmq_init(){
    for(int i=0;i<n;i++){
        for (int j=0;j<n;j++)stmax[i][j][0]=stmin[i][j][0]=num[i][j];
    }
    for(int kk=1;(1<<kk)<=n;kk++){
        for(int i=0;i+(1<<kk)-1<n;i++){
            for(int j=0;j+(1<<kk)-1<n;j++){
                stmax[i][j][kk]=max(max(stmax[i][j][kk-1],stmax[i+(1<<(kk-1))][j][kk-1]),max(stmax[i][j+(1<<(kk-1))][kk-1],stmax[i+(1<<(kk-1))][j+(1<<(kk-1))][kk-1]));
                stmin[i][j][kk]=min(min(stmin[i][j][kk-1],stmin[i+(1<<(kk-1))][j][kk-1]),min(stmin[i][j+(1<<(kk-1))][kk-1],stmin[i+(1<<(kk-1))][j+(1<<(kk-1))][kk-1]));
            }
        }
    }
}

int query(int x,int y,int xx,int yy){
    int lll=log(double(b))/log(2.0);
    int maxx=max(max(stmax[x][y][lll],stmax[xx-(1<<(lll))+1][y][lll]),max(stmax[x][yy-(1<<lll)+1][lll],stmax[xx-(1<<lll)+1][yy-(1<<lll)+1][lll]));
    int minn=min(min(stmin[x][y][lll],stmin[xx-(1<<(lll))+1][y][lll]),min(stmin[x][yy-(1<<lll)+1][lll],stmin[xx-(1<<lll)+1][yy-(1<<lll)+1][lll]));
    return maxx-minn;
}

int main()
{
    int x,y;
    scanf("%d%d%d",&n,&b,&k);
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            scanf("%d",&num[i][j]);
        }
    }
    rmq_init();
    for(int i=0;i<k;i++){
        scanf("%d%d",&x,&y);
        x--;y--;
        printf("%d\n",query(x,y,x+b-1,y+b-1));
    }
}

F - Interviewe HDU - 3486

面試,把n個人分成m段,每一段選擇值最大的那個人,問最少m爲多少能滿足所有選中的人的值加起來大於要求的指標。
滿足二分的條件,所以只需要二分m,然後求每一段的最大值加起來看滿足與否就行了。

//1 A,二分可行
#include<string.h>
#include<cstdio>
#include<queue>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=200005;
const int maxm=1005;
const int inf=0x3f3f3f3f;
int  n,k;
int num[maxn];
int st[maxn][20];

void rmq_init(){
    for(int i=0;i<n;i++){
        st[i][0]=num[i];
    }
    for(int j=1;(1<<j)<=n;j++){
        for(int i=0;i+(1<<j)-1<n;i++){
            st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
        }
    }
}

int query(int x,int l){
    int lll=log(double(l))/log(2);
    return max(st[x][lll],st[x+l-(1<<lll)][lll]);
}

bool solve(int m){
    int len=n/m;
    int ans=0;
    for(int i=0;i<m;i++){
        ans+=query(i*len,len);
    }
    if(ans>k)return 1;
    return 0;
}

int main()
{
    while(~scanf("%d%d",&n,&k),n+k!=-2){
        for(int i=0;i<n;i++){
            scanf("%d",&num[i]);
        }
        rmq_init();
        int lef=1,rig=n;
        int ans=-1;
        while(lef<=rig){
            int mid=(lef+rig)/2;
            if(solve(mid)){ans=mid;rig=mid-1;}
            else lef=mid+1;
        }
        printf("%d\n",ans);
    }
}

G - Find the hotel HDU - 3193

每一個旅館有一個價格p和距離q,要求找出所有沒有其他旅館的價格和距離都比他小的旅館,可以對其中一個值p或q排序,然後對於任意旅館,查找1~(pi-1)的區間內又沒有值小於qi,如果沒有則輸出。
不過其實不需要打ST表,遍歷時用兩個變量來維護最小值即可。

//我覺得這個方法更好,省空間,時間差不多
#include<string.h>
#include<cstdio>
#include<queue>
#include<map>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=10005;
const int maxm=1005;
const int inf=0x3f3f3f3f;
int  n,k;

pair<int,int> p[maxn];
vector<pair<int,int> > ans;
int main()
{
    while(~scanf("%d",&n)){
        ans.clear();
        for(int i=0;i<n;i++){
            scanf("%d%d",&p[i].first,&p[i].second);
        }
        sort(p,p+n);
        int totalmin=inf,cntmin=inf;
        int cnt=p[0].first;
        for(int i=0;i<n;i++){
            if(p[i].first!=cnt){
                cnt=p[i].first;
                totalmin=min(totalmin,cntmin);
            }
            cntmin=min(cntmin,p[i].second);
            if(totalmin>=p[i].second){
                ans.push_back(p[i]);
            }
        }
        sort(ans.begin(),ans.end());
        int ss=ans.size();
        printf("%d\n",ss);
        for(int i=0;i<ss;i++){
            printf("%d %d\n",ans[i].first,ans[i].second);
        }
    }
}

H - Check Corners HDU - 2888

二維矩陣區間最大值查詢以及最大值是否出現在矩陣四角。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100005;
const int maxm=10000005;
const int mod=998244353;
int n,m,q;
int st[301][301][9][9];
void rmq_init(){

    for(int a=0;(1<<a)<=n;a++){
        for(int b=0;(1<<b)<=m;b++){
            if(a+b==0)continue;
            for(int i=0;i+(1<<a)-1<n;i++){
                for(int j=0;j+(1<<b)-1<m;j++){
                    if(b)
                      st[i][j][a][b]=max(st[i][j][a][b-1],st[i][j+(1<<(b-1))][a][b-1]);
                    else 
                      st[i][j][a][b]=max(st[i][j][a-1][b],st[i+(1<<(a-1))][j][a-1][b]);
                }
            }
        }
    }
}

int query(int x1,int y1,int x2,int y2){
    int a=log(double(x2-x1+1))/log(2.0);
    int b=log(double(y2-y1+1))/log(2.0);
    return max(max(st[x1][y1][a][b],st[x1][y2-(1<<b)+1][a][b]),max(st[x2-(1<<a)+1][y1][a][b],st[x2-(1<<a)+1][y2-(1<<b)+1][a][b]));
}

int main(){
        while(~scanf("%d%d",&n,&m)){
            for(int i=0;i<n;i++){
                for(int j=0;j<m;j++){
                    scanf("%d",&st[i][j][0][0]);
                }
            }
        rmq_init();
       scanf("%d",&q);
        int a,b,c,d;
        while(q--){
            scanf("%d%d%d%d",&a,&b,&c,&d);
            a--,b--,c--,d--;
            int ans=query(a,b,c,d);
            bool f=0;
            if(st[a][b][0][0]==ans||st[c][d][0][0]==ans||st[a][d][0][0]==ans||st[c][b][0][0]==ans)f=1;
            printf("%d ",ans);
            if(f)printf("yes\n");
            else printf("no\n");
        }
        }
        return 0;
}

I - A Magic Lamp HDU - 3183

給你一串數字,可以刪除其中m個數字,問怎麼刪使得刪完後結果最小。
要使得高位儘量小,已知刪完後的位數,就知道刪完後的最高位可以是原序列的哪段區間,選取區間最小的那個,然後一次類推。

// i don't know why my solution is wa
#include<string.h>
#include<cstdio>
#include<queue>
#include<map>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a));
typedef long long ll;
const int maxn=1005;
const int maxm=1005;
const int inf=0x3f3f3f3f;
int  n,m;
char s[maxn];
int num[maxn];
int place[maxn][12];
int st[maxn][12];

void rmq_init(){
    for(int i=0;i<n;i++){st[i][0]=num[i];place[i][0]=i;}
    for(int j=1;(1<<j)<=n;j++){
        for(int i=0;i+(1<<j)-1<n;i++){
            if(st[i][j-1]<=st[i+(1<<(j-1))][j-1]){
                st[i][j]=st[i][j-1];
                place[i][j]=place[i][j-1];
            }
            else{
                st[i][j]=st[i+(1<<(j-1))][j-1];
                place[i][j]=place[i+(1<<(j-1))][j-1];
            }
        }
    }
}

int query(int x,int len){
    len=len-x+1;
    int lll=log(double(len))/log(2.0);
    if(st[x][lll]<=st[x+len-(1<<lll)][lll])return place[x][lll];
    else return place[x+len-(1<<lll)][lll];
}

int main()
{
    while(~scanf("%s%d",s,&m)){
        n=strlen(s);
        for(int i=0;i<n;i++)num[i]=s[i]-'0';
        rmq_init();
        if(m>=n){printf("0\n");continue;}
        int cnt=0;
        int kk=m;
        bool f=0;
        bool ssss=0;
        for(int i=0;i<n-m;i++){
                int pp=query(cnt,kk+i);
                cnt=pp+1;
                if(s[pp]!='0')f=1;
                if(f==0&&s[pp]=='0')continue;
                putchar(s[pp]);
                ssss=1;
        }
        if(ssss==0)printf("0");
        printf("\n");
 }
}

J - Phalanx HDU - 2859

找最大對稱子矩陣,也是用的DP來做,不懂得如何用RMQ做。

#include<iostream>
#include<stdio.h>
using namespace std;
int n;
char m[1005][1005];
int dp[1005][1005];
int main()
{
    while (~scanf("%d", &n), n){
        int ans = 1;
        for (int i = 0; i < n; i++){
            scanf("%s", m[i]);
            for (int j = 0; j < n; j++){
                dp[i][j] = 1;
            }
        }
        for (int i = 1; i < n; i++){
            for (int j = 0; j < n-1; j++){
                int k = 1;
                while (j + k < n&&i - k >= 0 && m[i][j + k] == m[i - k][j]){ k++; }
                if (k >= dp[i - 1][j + 1] + 1)dp[i][j] = dp[i - 1][j + 1] + 1;
                else{ dp[i][j] = k; }
                ans = max(ans, dp[i][j]);
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}
發佈了113 篇原創文章 · 獲贊 41 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章