【专题】用ST表解决RMQ刷题总结
看了一下上次写博客居然是好久以前的事了(我真是老懒狗了 )
开门见山,直接放专题链接和代码
kuangbin rmq专题
这个contest里面一共十道题但是实际上有2道dp的题混入其中,还有一道水题不用rmq就能做(不过做上头了也能强行rmq),还有一道HYSBZ上面的题因为懒就没有做。。。 然后就剩下6道题了,一道一道按难易和类型(一维rmq/二维rmq)摆代码
一维rmq(4道):
POJ3264
题意略。。。就是rmq板子题,有手就行
//POJ3264
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn=5e4+5;
int mx[maxn][17],mi[maxn][17];
int a[maxn],n,q;
void rmqinit(){
for(int i=1;i<=n;i++) mx[i][0]=mi[i][0]=a[i];
for(int j=1;j<=16;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]),
mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}
int rmq(int l,int r){
int d=log2(r-l+1);
int maxx=max(mx[l][d],mx[r-(1<<d)+1][d]);
int minn=min(mi[l][d],mi[r-(1<<d)+1][d]);
return maxx-minn;
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
rmqinit();
while(q--){
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",rmq(l,r));
}
}
POJ3368
题意:给你一个数列,有n个数a1,a2,….,an,并且是不减的,满足ai <= ai+1(1<=i<n)。现有m次询问,每个询问的结果为[l,r]区间内出现次数最多的数出现了多少次。
思路:无脑线段树维护区间最多和左右值及左右值出现次数 (既然是rmq专题,当然是rmq了) 由于数列有序是不减的所以相同的值在一起,用数组b[i]记录当前值a[i]是第几个出现的相同值,然后就可以rmq查询最大出现次数了,但是这样有一个问题就是如果查到的值是问询区间左端,前面有一部分不再区间里,所以再开一个c[]数组,倒着扫一下,记录当前与每个a[i]相同值的连续序列的右边界是什么,在查询的时候直接分左端l到c[l],再从c[l]+1到r进行rmq最后二者取max即可
//POJ3368
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=1e5+5;
int f[maxn][18],a[maxn],b[maxn],c[maxn],n,q;
void rmqinit(){
for(int i=1;i<=n;i++) f[i][0]=b[i];
for(int j=1;j<=17;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int rmq(int l,int r){
if(l>r) return 0;
int d=log2(r-l+1);
return max(f[l][d],f[r-(1<<d)+1][d]);
}
int main()
{
while(~scanf("%d",&n)&&n){
scanf("%d",&q);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
if(i==1||a[i]!=a[i-1]) b[i]=1;
else b[i]=b[i-1]+1;
}
int rbound=n;
for(int i=n;i>0;i--){
if(i==n||b[i]==b[i+1]-1) c[i]=rbound;
else c[i]=rbound=i;
}
rmqinit();
while(q--){
int l,r;
scanf("%d%d",&l,&r);
int ans1=min(c[l],r)-l+1;
int ans2=rmq(min(c[l],r)+1,r);
printf("%d\n",max(ans1,ans2));
}
}
}
HDU3183
题意:给一串数长度为n,让你从中删m个数,使得剩下的数刨去前导零之后最小
思路:这一上来当然想到的是无脑贪心啊,考虑只删一个数,最优是从前往后扫,当遇到一个相邻递减的数对,就把前面那个大的删掉,这个题的数据范围暴力也是可以过的,但是!rmq怎么做呢???看了别人的思路之后才懂,答案是n-m位数(保留前导零的话),先rmq找1到(m+1)里面最小的数(第id个),这个数肯定是答案的第一位,然后再找id+1到m+2最小的,这个是答案的第二位。。。。以此类推。
对了rmq维护的是区间最小值的下标,不是值,不想用三目运算符,所以就重载了min函数。。。
坑点:如果都删的话最后要输出0,不是空,wa了好久。。。
//HDU3183
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=1e3+5;
char ch[maxn];
int m,n,f[maxn][11],ans[maxn];
inline int min(int x,int y){
if(ch[x]==ch[y]) return x<y?x:y;
return ch[x]<ch[y]?x:y;
}
void rmqinit(){
for(int i=1;i<=n;i++) f[i][0]=i;
for(int j=1;j<=10;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int rmq(int l,int r){
int d=log2(r-l+1);
return min(f[l][d],f[r-(1<<d)+1][d]);
}
int main()
{
while(~scanf("%s%d",ch+1,&m)){
n=strlen(ch+1);
rmqinit();
int l=0,r=m+1,cnt=0;
while(r<=n){
l=rmq(l+1,r);
r++;
ans[++cnt]=l;
}
if(m==n){puts("0");continue;}
int st=1;
while(ch[ans[st]]=='0'&&st<cnt) st++;
for(int i=st;i<=cnt;i++) printf("%c",ch[ans[i]]);
puts("");
}
}
HDU3486
题意:给n个数,求最小的段数,使得每一段的最大值之和大于给定的k。每一段的长度相等,最后若干个丢掉。
思路:这题要求最少的区间数使得所有区间的最大值之和大于k,先预处理每个区间的最大值,然后枚举区间个数(二分理论上不对,但是能AC,emmm),计算和是否大于k,最小的区间数就是答案
//HDU3486
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=2e5+5;
int n,k,a[maxn],f[maxn][19];
void rmqinit(){
for(int i=1;i<=n;i++) f[i][0]=a[i];
for(int j=1;j<=18;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int rmq(int l,int r){
int d=log2(r-l+1);
return max(f[l][d],f[r-(1<<d)+1][d]);
}
int main()
{
while(~scanf("%d%d",&n,&k)){
if(n<0&&k<0) break;
int sum=0,maxx=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum+=a[i],maxx=max(maxx,a[i]);
}
if(sum<=k){
puts("-1");
continue;
}
rmqinit();
int i;
for(i=max(1,(k+1)/maxx);i<=n;i++){
int len=n/i;
int tot=0;
for(int j=1;j<=i;j++){
tot+=rmq((j-1)*len+1,j*len);
if(tot>k) break;
}
if(tot>k) break;
}
printf("%d\n",i);
}
}
二维rmq(2道):
其实二维rmq和一维rmq的思想差不多,用于解决二维区间最值问题,有两种不同的实现方法
法一:1) O(nmlog(m))预处理----O(n)查询,把每一行都当成一维RMQ处理
法二:2) O(nmlog(n)*log(m)预处理----O(1)查询,f[ i ][ j ][ k ][ l ]表示从第一个点也就是左上角(i,j)起,到右下角(i+2^k, j+2^l)但是不包括右下角和右列和下行 ,然后预处理和查询的时候都把大矩形分成四个小矩形。
【注意】预处理的时候除了和一维rmq一样处理f[i][j][0][0],同时千万不要忘了和一维一样的方法处理f[i][j][0][x]和f[i][j][x][0]的情形。
POJ2019
题意:没什么好说的了,就是板子题,只不过问询区间是正方形边长是固定的b,给左上角坐标查询最大与最小的差
//POJ2019
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=255;
int n,b,k;
int mx[maxn][maxn][9][9],mi[maxn][maxn][9][9];
int a[maxn][maxn];
void rmqinit(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mx[i][j][0][0]=mi[i][j][0][0]=a[i][j];
for(int j=1;j<=n;j++)
for(int k1=1;k1<=8;k1++)
for(int i=1;i+(1<<k1)-1<=n;i++)
mx[i][j][k1][0]=max(mx[i][j][k1-1][0],mx[i+(1<<(k1-1))][j][k1-1][0]),
mi[i][j][k1][0]=min(mi[i][j][k1-1][0],mi[i+(1<<(k1-1))][j][k1-1][0]);
for(int i=1;i<=n;i++)
for(int k2=1;k2<=8;k2++)
for(int j=1;j+(1<<k2)-1<=n;j++)
mx[i][j][0][k2]=max(mx[i][j][0][k2-1],mx[i][j+(1<<(k2-1))][0][k2-1]),
mi[i][j][0][k2]=min(mi[i][j][0][k2-1],mi[i][j+(1<<(k2-1))][0][k2-1]);
for(int k1=1;k1<=8;k1++)
for(int k2=1;k2<=8;k2++)
for(int i=1;i+(1<<k1)-1<=n;i++)
for(int j=1;j+(1<<k2)-1<=n;j++)
mx[i][j][k1][k2]=max(max(mx[i][j][k1-1][k2-1],mx[i+(1<<(k1-1))][j][k1-1][k2-1]),max(mx[i][j+(1<<(k2-1))][k1-1][k2-1],mx[i+(1<<(k1-1))][j+(1<<(k2-1))][k1-1][k2-1])),
mi[i][j][k1][k2]=min(min(mi[i][j][k1-1][k2-1],mi[i+(1<<(k1-1))][j][k1-1][k2-1]),min(mi[i][j+(1<<(k2-1))][k1-1][k2-1],mi[i+(1<<(k1-1))][j+(1<<(k2-1))][k1-1][k2-1]));
}
int rmq(int x1,int y1,int x2,int y2){
int d1=log2(x2-x1+1);
int d2=log2(y2-y1+1);
int maxx=max(max(mx[x1][y1][d1][d2],mx[x2-(1<<d1)+1][y1][d1][d2]),max(mx[x1][y2-(1<<d2)+1][d1][d2],mx[x2-(1<<d1)+1][y2-(1<<d2)+1][d1][d2]));
int minn=min(min(mi[x1][y1][d1][d2],mi[x2-(1<<d1)+1][y1][d1][d2]),min(mi[x1][y2-(1<<d2)+1][d1][d2],mi[x2-(1<<d1)+1][y2-(1<<d2)+1][d1][d2]));
return maxx-minn;
}
int main()
{
scanf("%d%d%d",&n,&b,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
rmqinit();
while(k--){
int r,c;
scanf("%d%d",&r,&c);
printf("%d\n",rmq(r,c,r+b-1,c+b-1));
}
}
HDU2888
题意:给定一个n * m的矩阵,再给定q个询问,每次询问(r1,c1)为左上角,(r2,c2)为右下角的子矩形的最大值,并且判断该最大值是否出现在了这个子矩阵的4个顶角上
思路:也是板子题。。。
//HDU2888
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=305;
int m,n,q;
int mx[maxn][maxn][9][9];
void rmqinit(){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&mx[i][j][0][0]);
for(int j=1;j<=m;j++)
for(int k1=1;k1<=8;k1++)
for(int i=1;i+(1<<k1)-1<=n;i++)
mx[i][j][k1][0]=max(mx[i][j][k1-1][0],mx[i+(1<<(k1-1))][j][k1-1][0]);
for(int i=1;i<=n;i++)
for(int k2=1;k2<=8;k2++)
for(int j=1;j+(1<<k2)-1<=m;j++)
mx[i][j][0][k2]=max(mx[i][j][0][k2-1],mx[i][j+(1<<(k2-1))][0][k2-1]);
for(int k1=1;k1<=8;k1++)
for(int k2=1;k2<=8;k2++)
for(int i=1;i+(1<<k1)-1<=n;i++)
for(int j=1;j+(1<<k2)-1<=m;j++)
mx[i][j][k1][k2]=max(max(mx[i][j][k1-1][k2-1],mx[i+(1<<(k1-1))][j][k1-1][k2-1]),max(mx[i][j+(1<<(k2-1))][k1-1][k2-1],mx[i+(1<<(k1-1))][j+(1<<(k2-1))][k1-1][k2-1]));
}
int rmq(int x1,int y1,int x2,int y2){
int d1=log2(x2-x1+1);
int d2=log2(y2-y1+1);
return max(max(mx[x1][y1][d1][d2],mx[x2-(1<<d1)+1][y1][d1][d2]),max(mx[x1][y2-(1<<d2)+1][d1][d2],mx[x2-(1<<d1)+1][y2-(1<<d2)+1][d1][d2]));
}
int main()
{
while(~scanf("%d%d",&n,&m)){
rmqinit();
for(scanf("%d",&q);q;q--){
int r1,c1,r2,c2;
scanf("%d%d%d%d",&r1,&c1,&r2,&c2);
int maxx=rmq(r1,c1,r2,c2);
printf("%d ",maxx);
if(mx[r1][c1][0][0]==maxx||mx[r1][c2][0][0]==maxx||mx[r2][c1][0][0]==maxx||mx[r2][c2][0][0]==maxx) puts("yes");
else puts("no");
}
}
}