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模型在求解出答案。