范围最小值问题(Range Minimum Query)
算法简述
给出一个数组,设计一个数据结构,支持查询操作,计算A[l]到A[r]的最小值。每次用一个循环来计算显然不够快,前缀和思想也不能提高效率。可以写一个线段树,但是预处理和查询的复杂度都是O(logn)。实践中最常用的是Tarjan的Sparse-Table算法,预处理时间是O(nlogn),但是查询只需要O(1),而且常数很小。最重要的是这个算法非常好写,并且不容易写错。
预处理
使用dp的思想方法。设a[i]是要求区间最值的数列,f[i,j]表示从第i个数起连续2^j个数中的最大值。例如数列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……从这里可以看出f[i,0]其实就等于a[i]。这样,Dp的状态、初值都已经有了,剩下的就是状态转移方程。我们把f[i,j]平均分成两段(因为f[i,j]一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。f[i,j]就是这两段的最大值中的最大值。于是我们得到了动规方程F[i,j]=max(F[i,j-1],F[i+2^(j-i),j-1])。
查询操作
找到最大的k使得2^k<=(r-l+1),这样以l开头,r结尾的长度为2^k的两个区间就可以完全覆盖查询区间。
例题:hdu 3183
对于序列A[1...N],选取N-M个数,使组成的值最小,而且顺序不能交换,既然要选取N-M个,那么可以容易知道这N-M位数的第一位一定在数组A中的区间[1,M+1]中出现,为什么是这样呢?
假设A数组就只有6个数,分别是A[1],A[2],A[3],A[4],A[5],A[6],我们去掉M=2个数,使形成的值最小。那么我们此时的N=6,M=2,N-M=4。则我们说形成的4位数的第一位一定在区间[1,3]中出现,因为如果区间范围再大点,比如[1,4],这样就不科学了,因为第一位一定不会是A[4],更不会是A[5],A[6],我们假设可以是A[4],那么后面只有A[5],A[6]两位数了,这样的话最多只可能形成3位数,绝对不能形成N-M=4位了。
当然到了这里,我们就可以这样做了,第一位可以在区间[1,M+1]里面找,假设第一位在位置x,因为第二位肯定在第一位的后面,所以第二位一定存在于区间[x+1,M+2],为什么是M+2,因为第一位已经确定了,现在只需要确定N-M-1位了,所以区间就可以向后增加1,一直这样循环下去,就可以找到了。(借鉴大牛的做法)
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAX = 1010;
int n,m;
int dp[MAX][MAX];
char a[MAX],num[1010];
int Min(int i, int j){//返回小的元素的下标 便于操作
if(a[i] <= a[j]) return i;
return j;
}
void ST(){
for(int i=0; i<n; i++) dp[i][0] = i;
for(int j=1; j<=(int)(log((double)n)/log(2.0)); j++)//换底公式
for(int i=0; (i+(1<<j)-1)<n; i++)
dp[i][j] = Min(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
}
int Query(int l, int r){
int k = (int)(log((double)(r-l+1))/log(2.0));
return Min(dp[l][k], dp[r-(1 << k)+1][k]);
}
int main(){
while(scanf("%s %d",a,&m) != EOF){
n = strlen(a);
m = n - m;
ST();
int i,j;
i = j = 0;
while(m--){
i = Query(i, n-m-1);
num[j++] = a[i++];
}
for(i=0; i<j; i++)
if(num[i] != '0') break;
if(i == j) {puts("0"); continue;}
while(i < j){
printf("%c",num[i]);
i++;
}
puts("");
}
return 0;
}