[关闭]
@zsh-o 2018-03-20T17:28:24.000000Z 字数 2104 阅读 954

ST RMQ 区间最值 hihoCoder 1068 1710

算法 hihoCoder


用以求解区间最值,原理是DP,但单纯用DP的话,构建表的过程的时间复杂度为,为了压缩建表的时间复杂度,大哥们根据二分策略把时间复杂度降到

F[i,j]:从第个数开始的个数满足的极值数(最小值,最大值,或者某种计数),这样就把区间分成了等长的两部分,因此迭代公式为


F[i][0]为初始化的原值,长度为

  1. scanf("%d", &N);
  2. for(int i=1; i<=N; i++){
  3. scanf("%d", &F[i][0]);
  4. }
  1. int log2(int x){
  2. int n = 0;
  3. while((1<<n)<=x){
  4. n++;
  5. }
  6. return n-1;
  7. }
  8. void init(){
  9. int length = log2(N);
  10. for(int j=1; j<=length; j++){
  11. for(int i=1; i<=N-(1<<j)+1; i++){
  12. F[i][j] = min(F[i][j-1], F[i+(1<<(j-1))][j-1]);
  13. }
  14. }
  15. }

我们要查询,但dp表中代表的长度均为2的幂,故,采取的方法为把要查询的区间分为两部分,每一部分长度均为的幂,最后返回两者的较小值,该长度计算公式为log2(b-a+1)

  1. int query(int i, int j){
  2. int k = log2(j-i+1);
  3. return min(F[i][k], F[j-(1<<k)+1][k]);
  4. }

hihoCoder 1710

题意

给定N个整数,小Hi会询问你M个问题。

对于每个问题小Hi给出两个整数(),请你找出中最长的等差连续子数列,并输出其长度。

例如[2, 3, 5, 7, 9]中最长的等差连续子数列是[3, 5, 7, 9]长度为4。

分析

当时做的时候看错题了,没有注意其要求是等差连续子序列,下面分析,有和没有这个条件的解法

有连续条件的限制之后问题变得很简单,首先算出差值数组


因为是连续的,再建一个数组保存SUB中相同串的长度

  1. scanf("%d %d", &N, &M);
  2. for(int i=1; i<=N; i++){
  3. scanf("%d", &A[i]);
  4. }
  5. SUB[1] = INT_MAX;
  6. NUMM[1] = 1;
  7. for(int i=2; i<=N; i++){
  8. SUB[i] = A[i] - A[i-1];
  9. NUMM[i] = SUB[i]==SUB[i-1]? NUMM[i-1]+1 : 2;
  10. }

给定区间NUMM数组计算区间内等差连续子序列的长度

先介绍一种最容易想的方法:直接算。。。

  1. int query(int a, int b){
  2. int t = b;
  3. int result = 0;
  4. while(a<t){
  5. t = b - NUMM[b] + 1;
  6. if(NUMM[b]>result){
  7. if(t<a){
  8. result = max(b - a + 1, result); //防止当前的最大值发生在边界
  9. }else{
  10. result = NUMM[b];
  11. }
  12. }
  13. b = t;
  14. }
  15. return result;
  16. }

另一种利用RMQ,只不过,RMQ中保存的是区间中最大数的下标,因为有三种情况,假设查询的区间为,最长等差连续子序列可能满足三种情况

根据NUMM数组寻找区间最大值下标

  1. void init(){
  2. for(int i=1; i<=N; i++){
  3. F[i][0] = i;
  4. }
  5. int t = log2(N);
  6. for(int j=1; j<=t; j++){
  7. for(int i=1; i<=N-(1<<j)+1; i++){
  8. int a1 = F[i][j-1];
  9. int a2 = F[i+(1<<(j-1))][j-1];
  10. F[i][j] = NUMM[a1] >= NUMM[a2]? a1 : a2;
  11. }
  12. }
  13. }
  14. int query(int a, int b){
  15. int t = log2(b-a+1);
  16. int a1 = F[a][t];
  17. int a2 = F[b-(1<<t)+1][t];
  18. return NUMM[a1] >= NUMM[a2]? a1 : a2;
  19. }

情况一,该串正好完全在区间内部,这时所求的长度就是
image_1c91b9sgg1vvu1nik37jfsk4sl19.png-7.8kB

情况二,该串与区间左相交,这时该串满足条件的长度只是相交部分要小于,所以这时要求右边不想交部分的最大值,再与相交部分长度比较
image_1c91bcf6a1m83uae1td54ljfjv1m.png-7.3kB

第三种更独特的情况是,左相交,并且有边界与重合,这时所求长度就是区间长度
image_1c91blfh918r6ursqlaso41pru23.png-7.3kB

  1. int a, b;
  2. for(int i=0; i<M; i++){
  3. scanf("%d %d", &a, &b);
  4. int idx = query(a, b);
  5. if(a <= idx - NUMM[idx] +1)
  6. printf("%d\n", NUMM[idx]);
  7. else{
  8. int t = idx - a + 1;
  9. if(idx == b)
  10. printf("%d\n", t);
  11. else
  12. printf("%d\n", max(t, NUMM[query(idx+1, b)]));
  13. }
  14. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注