Fork me on GitHub

rmq入门1

1068 : RMQ-ST算法

描述:

小Hi和小Ho在美国旅行了相当长的一段时间之后,终于准备要回国啦!而在回国之前,
他们准备去超市采购一些当地特产——比如汉堡(大雾)之类的回国。

但等到了超市之后,小Hi和小Ho发现者超市拥有的商品种类实在太多了——他们实在看不过来了!
于是小Hi决定向小Ho委派一个任务:假设整个货架上从左到右拜访了N种商品,并且依次标号为1到N,
每次小Hi都给出一段区间[L, R],小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,
并且告诉小Hi这个商品的重量,于是他们就可以毫不费劲的买上一大堆东西了——多么可悲的选择困难症患者。

(虽然说每次给出的区间仍然要小Hi来进行决定——但是小Hi最终机智的选择了使用随机数生成这些区间!
但是为什么小Hi不直接使用随机数生成购物清单呢?——问那么多做什么!)
提示一:二分法是宇宙至强之法!(真的么?)

输入:

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2行为N个整数,分别描述每种商品的重量,
其中第i个整数表示标号为i的商品的重量weight_i。

每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数。

每组测试数据的第N+4~N+Q+3行,每行分别描述一个询问,其中第N+i+3行为两个整数Li, Ri,
表示小Hi询问的一个区间[Li, Ri]。

对于100%的数据,满足N<=10^6,Q<=10^6, 1<=Li<=Ri<=N,0<weight_i<=10^4。

输出:

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,
表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。

样例输入:

10
7334
1556
8286
1640
2699
4807
8068
981
4120
2179
5
3 4
2 8
2 4
6 8
7 10

样例输出:

1640
981
1556
981
981

思路:

这是一道模板题,要注意的是要用scanf,printf输出不然会超时.

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int a[maxn];
int Min[maxn][32];
void Rmq_int(int n){
    for(int i=1;i<=n;i++){
        Min[i][0]=a[i];
    }
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
            Min[j][i]=min(Min[j][i-1],Min[j+(1<<(i-1))][i-1]);
        }
    }
}
int Rmq_search(int l,int r){
    int k=0;
    while((1<<k)<=r-l+1)
        k++;
        //cout<<k<<endl;
    int h=min(Min[l][k-1],Min[r-(1<<(k-1))+1][k-1]);
    //cout<<h<<endl;
    return h;
}
int main(){
    int n;
    while(scanf("%d",&n)!=EOF){
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    Rmq_int(n);
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        int k=Rmq_search(a,b);
        printf("%d\n",k);
    }
    }
    return 0;
}

总结:

rmq以前了解过现在才真正有机会来熟悉,它运用在区间查询非常好,
但遇到修改的话就要用到线段树比较好,

在创建查询数组的时候要注意边界,在查询的时候也一样。

1070:RMQ问题再临

描述:

终于,小Hi和小Ho踏上了回国的旅程。在飞机上,望着采购来的特产——小Hi陷入了沉思:还记得在
上上周他们去超市的时候,前前后后挑了那么多的东西,都幸运的没有任何其他人(售货员/其他顾客)
来打搅他们的采购过程。但是如果发生了这样的事情,他们的采购又会变得如何呢?

于是小Hi便向小Ho提出了这个问题:假设整个货架上从左到右摆放了N种商品,
并且依次标号为1到N,
每次小Hi都给出一段区间[L, R],
小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,
并且告诉小Hi这个商品的重量。但是在这个过程中,可能会因为其他人的各种行为,
对某些位置上的商品的重量产生改变(如更换了其他种类的商品),面对这样一个问题,
小Ho又该如何解决呢?

输入:

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量weight_i。

每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数与商品的重量被更改的次数之和。

每组测试数据的第N+4~N+Q+3行,每行分别描述一次操作,每行的开头均为一个属于0或1的数字,
分别表示该行描述一个询问和描述一次商品的重量的更改两种情况。对于第N+i+3行,
如果该行描述一个询问,
则接下来为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri];
如果该行描述一次商品的重量的更改,则接下来为两个整数Pi,Wi,
表示位置编号为Pi的商品的重量变更为Wi

对于100%的数据,满足N<=10^4,Q<=10^4, 1<=Li<=Ri<=N,1<=Pi<=N, 0<weight_i, Wi<=10^4。

输出:

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,
表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。

样例输入:

10
618 5122 1923 8934 2518 6024 5406 1020 8291 2647 
6
0 3 6
1 2 2009
0 2 2
0 2 10
1 1 5284
0 2 5

样例输出:

1923
2009
1020
1923

思路:

因为是10^4所以直接枚举就好。

代码:

#include<bits/stdc++.h>

using namespace std;
const int maxn=1e4+50;
int a[maxn];
int n;
int query(int l,int r){
    int Min=1e6;
    for(int i=l;i<=r;i++){
        if(Min>a[i])
            Min=a[i];
    }
    return Min;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int q,l,r;
        scanf("%d%d%d",&q,&l,&r);
        if(q==0){
           int Min=query(l,r);
           printf("%d\n",Min);
        }else{
            a[l]=r;
        }

    }
    return 0;
}

总结:

这道题的数据范围在枚举可做的范围类,直接枚举。也可用rmq但是没必要下一道就要用到线段树。

1077:RMQ问题再临-线段树

描述:

上回说到:小Hi给小Ho出了这样一道问题:假设整个货架上从左到右摆放了N种商品,
并且依次标号为1到N,每次小Hi都给出一段区间[L, R],
小Ho要做的是选出标号在这个区间内的所有商品重量最轻的一种,
并且告诉小Hi这个商品的重量。但是在这个过程中,可能会因为其他人的各种行为,
对某些位置上的商品的重量产生改变(如更换了其他种类的商品)。
小Ho提出了两种非常简单的方法,但是都不能完美的解决。那么这一次,
面对更大的数据规模,小Ho将如何是好呢?

输入:

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2行为N个整数,分别描述每种商品的重量,
其中第i个整数表示标号为i的商品的重量weight_i。

每组测试数据的第3行为一个整数Q,表示小Hi总共询问的次数与商品的重量被更改的次数之和。

每组测试数据的第N+4~N+Q+3行,每行分别描述一次操作,
每行的开头均为一个属于0或1的数字,分别表示该行描述一个询问和描述一次商品的重量的更改两种情况。
对于第N+i+3行,如果该行描述一个询问,则接下来为两个整数Li, Ri,
表示小Hi询问的一个区间[Li, Ri];如果该行描述一次商品的重量的更改,
则接下来为两个整数Pi,Wi,表示位置编号为Pi的商品的重量变更为Wi

对于100%的数据,满足N<=10^6,Q<=10^6, 1<=Li<=Ri<=N,1<=Pi<=N, 0<weight_i, Wi<=10^4。

输出:

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,
表示查询的结果:标号在区间[Li, Ri]中的所有商品中重量最轻的商品的重量。

样例输入:

10
3655 5246 8991 5933 7474 7603 6098 6654 2414 884 
6
0 4 9
0 2 10
1 4 7009
0 5 6
1 3 7949
1 3 1227

样例输出:

2414
884
7474

思路:

如果直接用rmq就会每次用o(n)的时间复杂度修改那么总复杂度o(n^2).如果用线段树修改和查询都用logn
虽然查询复杂度升高了但总复杂度降低了。

#代码:

#include<bits/stdc++.h>

using namespace std;
const int maxn=1e6+5;
int a[maxn];
struct node{
    int left,right;
    int num;
};
node s[2*maxn];
int numid=0;
void bulidtree(int id,int l,int r){//建树
    s[id].left=l;
    s[id].right=r;
    if(l==r){
        s[id].num=a[++numid];
        return ;
    }
    bulidtree(2*id,l,(l+r)/2);
    bulidtree(2*id+1,(l+r)/2+1,r);
    s[id].num=min(s[2*id].num,s[2*id+1].num);
    return ;
}
void updatatree(int id,int p,int w){//更新节点
           if(s[id].right==s[id].left){
                s[id].num=w;
                return ;
           }
           int mid=(s[id].left+s[id].right)/2;
           if(p<=mid){
            updatatree(2*id,p,w);
           }else {
               updatatree(2*id+1,p,w);
           }
           s[id].num=min(s[2*id].num,s[2*id+1].num);
           return ;
}
int query(int id,int l,int r){//查询区间
    if(s[id].left==l&&s[id].right==r){
         return s[id].num;
    }
    int mid=(s[id].left+s[id].right)/2;
    //cout<<mid<<endl;
    if(r<=mid){
            return query(2*id,l,r);
    }
    if(l>mid){
        return query(2*id+1,l,r);
    }
    return min(query(2*id,l,mid),query(2*id+1,mid+1,r));
}
int main(){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        numid=0;
        bulidtree(1,1,n);
        int m;
        scanf("%d",&m);
        for(int i=1;i<=m;i++){
            int p;
            scanf("%d",&p);
            if(p==0){
                int l,r;
                scanf("%d%d",&l,&r);
               int num=query(1,l,r);
               printf("%d\n",num);
            }else{
                int p,w;
                scanf("%d%d",&p,&w);
                updatatree(1,p,w);
            }
        }

}

总结:

线段树的节点总数是你输入数n的两倍,线段树点的修改是通过改节点来把整个区间更新,
还有一些区间修改,增加,删除等操作下次补上。

1306:股票价格

描述:

小Hi最近在分析一支股票的价格走势,他需要一个程序来辅助分析。这个程序会接收3种消息(指令):

价格信息,格式是P timestamp price:表示这支股票在 timestamp 时刻价格是 price。

删除价格指令,格式是R timestamp:随着时间推移,小Hi会积累越来越多的价格数据。
一些老旧的数据会变得不重要。这个指定会删除 timestamp 
以前(包括 timestamp 时刻)的价格数据。

价格查询指令,格式是Q:小Hi希望程序返回这只股票最高、最低和最近的价格。
注意已经被删除的价格不应该被统计。

给定一个包含以上3种信息(指令)的序列,你能否帮助小Hi完成这个程序呢?

输入:

第1行包含一个整数 N (1 ≤ N ≤ 500000),表示消息(指令)序列的长度。

第2 - N+1行,每行包含一条消息或指令。

输入保证价格信息是按照 timestamp 升序排列的,并且出现的 timestamp 和价格小于100000000。

输出:

对于输入中每一条价格查询指令,输出当时最高、最低和最近的价格。

样例输入:

10
P 1 77
P 2 73
P 5 70
P 7 74
Q
R 4
Q
P 8 78
R 5
Q

样例输出:

77 70 74
74 70 74
78 74 78

思路:

这道题可以直接用set,map来解决map以时间为键值price为值map里面是自动排序的,
在构建一个time,price结构体存入set中map,set的增删改都是logn在这道题是可以接受的。

代码:

#include<bits/stdc++.h>

using namespace std;
string s;
map<int,int>mp;
set<pair<int,int>>st;
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>s;
        if(s=="Q"){
            cout<<st.rbegin()->first<<" "<<st.begin()->first<<" "<<mp.rbegin()->second<<endl;
        }else if(s=="P"){
            int time,price;
            cin>>time>>price;
            st.insert(make_pair(price,time));
            mp[time]=price;
        }else{
            int time;
            cin>>time;
            while(mp.begin()->first<=time){
                st.erase(make_pair(mp.begin()->second,mp.begin()->first));
                mp.erase(mp.begin());

            }
        }
    }
    return 0;
}

总结:

这道题让我意识到stl学得好在某些时候有很大的帮助。

1074 :字体设计

描述:

现在需要给上图里的黄点做「位置锚固」,然而有些黄点的位置可以由「插值(IUP)」确定,
这样就可以减少锚固的数量。

例如,在上图中,#46 #49 #52 #53 的位置可以由 #42 和 #57 插值得到, 
我就不需要去锚固它们,只需要固定 #42 和 #57 就可以了。 可以给这个字减少至少 12 字节 ……

抽象一下问题,现在给出输入数组 a,

定义 ax 可以被 al 和 ar 插值得到为:

存在 l < x < r

使得 al ≤ ax ≤ ar 或者 al ≥ ax ≥ ar。

求最少的「锚固」元素的数目,使得非锚固元素都可以由其左右最靠近它的锚固元素插值得到。
并输出锚固元素的下标。

输入:

第一行输入一个数 n,表示数组的大小(1 ≤ n ≤ 105)。 接下来的一行,输入一行 n 个整数
a1, a2, ..., an,表示数组中的元素(1 ≤ ai ≤ 109)。所有 ai 互不相同。

输出:

输出的第一行包含一个整数 ans,表示锚固元素的数目。 接下来一行包含 ans 个递增的整数,
表示锚固元素的下标。

样例输入:

8
3 4 2 1 8 5 7 6

样例输出:

7
1 2 4 5 6 7 8

思路:、

这道题首先你要找到整个区间的最大值和最小值的位置,然后再更据他们的位置在去找其他区间的最大或最小值
这里假设 a1<a2 a1代表最大值得位置,a2代表最小值的位置 让后你根据a1找左边的最小值的位置a3在更根据
a3找左边的最大值,a2的一样。

代码:

#include<bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
int a[maxn];
int Min[maxn][32];
int Max[maxn][32];
int n;
map<int,int>mp;
vector<int>st;
void Rmq_int(){
    for(int i=1;i<=n;i++){
        Min[i][0]=a[i];
        Max[i][0]=a[i];
    }
    for(int i=1;(1<<i)<=n;i++)
        for(int j=1;j+(1<<i)-1<=n;j++){
            Min[j][i]=min(Min[j][i-1],Min[j+(1<<(i-1))][i-1]);
            Max[j][i]=max(Max[j][i-1],Max[j+(1<<(i-1))][i-1]);
            //cout<<j<<" "<<i<<" "<<Min[j][i]<<" "<<Max[j][i]<<endl;
    }
    return ;
}
int query(int l,int r,int type){
     int t=0;
    while((1<<(t+1))<=(r-l)){
        t++;
    }
    //cout<<t<<endl;
    if(type==1){
        int h=max(Max[l][t],Max[r-(1<<t)+1][t]);
        return mp[h];
    }else{
        int h=min(Min[l][t],Min[r-(1<<t)+1][t]);
         return mp[h];
    }
}
int Search(int pos1,int pos2,int type){
    //cout<<pos1<<" "<<pos2<<endl;
   return query(pos1,pos2,type);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        mp[a[i]]=i;
    }
    Rmq_int();
    int x=query(1,n,1);
    int y=query(1,n,2);
    //cout<<x<<" "<<y<<endl;
    if(x<y){
        st.push_back(x);
       while(1){
            if(x>1){
                x=Search(1,x-1,2);
                st.push_back(x);
            }else{
                break;
            }
            if(x>1){
                x=Search(1,x-1,1);
                st.push_back(x);
            }else{
                break;
            }
       }
       st.push_back(y);
       while(1){
            if(y<n){
                y=Search(y+1,n,1);
                st.push_back(y);
            }else{
                break;
            }
            if(y<n){
                y=Search(y+1,n,2);
                st.push_back(y);
            }else{
                break;
            }
       }
    }else{
        st.push_back(x);
        while(1){
            if(x<n){
                x=Search(x+1,n,2);
                //cout<<x<<endl;
                st.push_back(x);
            }else{
                break;
            }
            if(x<n){
                x=Search(x+1,n,1);
                st.push_back(x);
            }else{
                break;
            }
        }
        st.push_back(y);
        while(1){
            if(y>1){
                y=Search(1,y-1,1);
                st.push_back(y);
            }else{
                break;
            }
            if(y>1){
                y=Search(1,y-1,2);
                st.push_back(y);
            }else{
                break;
            }
        }
    }
    sort(st.begin(),st.end());
    printf("%d\n",st.size());
    for(int i=0;i<st.size();i++){
        if(i!=st.size()-1){
            printf("%d ",st[i]);
        }else{
            printf("%d\n",st[i]);
        }
    }
    return 0;
}

总结:

这道题不在是裸的rmq而是要通过转换成rmq模型在求解出答案。