POJ 3419 Difference Is Beautiful(RMQ变形)

题意:N个数,M个询问,每个询问为一个区间,求区间最长连续子序列,要求每个数都不同(perfect sequence,简称PS)。

题解:很容易求出以每个数为结尾的ps,也就是求区间的最大值。有一个不同就是长度可能会超出询问范围,所以先对PS的首位置二分,然后RMQ。注意一点,序列有可能出现负数,所以先加最大值变为正数。其实也不算变形,挺裸的……

这题卡线段树,然而我只会线段树,心塞……

代码(树状数组):

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2000005;

int a[N], pos[N], len[N], nt[N];

#define lowbit(x) ((x)&(-x))
int idx[N];

void init(int n)
{
    for(int i=1;i<=n;i++) {
        idx[i] = len[i];
        for(int j=1;j<lowbit(i);j<<=1){
            idx[i]=max(idx[i],idx[i-j]);
        }
    }
}

int query(int l, int r)
{
    int ans=len[r];
    while(true) {
        ans=max(ans,len[r]);
        if(r==l) break;
        for(r-=1;r-l>=lowbit(r);r-=lowbit(r)){
            ans=max(ans,idx[r]);
        }
    }
    return ans;
}

int main()
{
    int n, m;
    while (~scanf("%d%d", &n, &m)) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            a[i] += 1000000;
        }
        memset(pos, 0, sizeof pos);
        nt[0] = 1;
        for (int i = 1; i <= n; ++i) {
            nt[i] = max(nt[i-1], pos[a[i]]+1);//nt[i]以i为结尾的最长子序列的首端
            len[i] = i-nt[i]+1;
            pos[a[i]] = i;
        }
        init(n);

        while (m--) {
            int l, r;
            scanf("%d%d", &l, &r); l++, r++;
            int p = upper_bound(nt+1, nt+1+n, l) - nt;
            int ans = 0;
            if (p > r) {
                printf("%d
", r-l+1);
                continue;
            }
            if (p > l) ans = p-l;
            ans = max(ans, query(p, r));
            printf("%d
", ans);
        }
    }
    return 0;
}

代码(ST算法):

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2000005;

int a[N], pos[N], len[N], nt[N];
int f[N][20];

void init(int n)
{
    // f[i,j]表示[i,i+2^j-1]区间最大值
    // f[i,j]=max(d[i,j-1], d[i+2^(j-1),j-1])
    for (int i = 1; i <= n; ++i) f[i][0] = len[i];
    for (int j = 1; (1<<j) <= n; ++j)
        for (int i = 1; i+j-1 <= n; ++i)
            f[i][j] = max(f[i][j-1], f[i+(1<<j-1)][j-1]);
}

int query(int l, int r)
{
    int k = 0;
    while ((1<<k+1 <= r-l+1)) ++k;
    return max(f[l][k], f[r-(1<<k)+1][k]);
}


int main()
{
    int n, m;
    while (~scanf("%d%d", &n, &m)) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            a[i] += 1000000;
        }
        memset(pos, 0, sizeof pos);
        nt[0] = 1;
        for (int i = 1; i <= n; ++i) {
            nt[i] = max(nt[i-1], pos[a[i]]+1);//nt[i]以i为结尾的最长子序列的首端
            len[i] = i-nt[i]+1;
            pos[a[i]] = i;
        }
        init(n);

        while (m--) {
            int l, r;
            scanf("%d%d", &l, &r); l++, r++;
            int p = upper_bound(nt+1, nt+1+n, l) - nt;
            int ans = 0;
            if (p > r) {
                printf("%d
", r-l+1);
                continue;
            }
            if (p > l) ans = p-l;
            ans = max(ans, query(p, r));
            printf("%d
", ans);
        }
    }
    return 0;
}

还有一种网上流传的算法,类似kmp。想法很巧妙,但是最坏复杂度貌似是O(n^2),不过也能A这道题。而且速度不必上面慢。数据水吧~

nt[i]记录想要字串中含有a[(i+1)-pos[i+1]]的最大位置。

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 2000005;

int a[N], pos[N], len[N], nt[N];

int main()
{
    int n, m;
    while (~scanf("%d%d", &n, &m)) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d", a+i);
            a[i] += 1000000;
        }
        memset(pos, 0, sizeof pos);
        len[0] = 0; nt[0] = 0;
        for (int i = 1; i <= n; ++i) {
            if (len[i-1]+1 <= i-pos[a[i]]) {
                len[i] = len[i-1]+1;
                nt[i] = nt[i-1];
            } else {
                len[i] = i-pos[a[i]];
                nt[i] = i-1;
            }
            pos[ a[i] ] = i;
            //printf("%d %d %d
", len[i], nt[i], pos[a[i]]);
        }
        while (m--) {
            int l, r;
            scanf("%d%d", &l, &r); l++, r++;
            int res = len[r];
            if (res >= r-l+1) {
                printf("%d
", r-l+1);
                continue;
            }
            while (nt[r] > 0) {
                r = nt[r];
                res = max(res, min(len[r], r-l+1));
                if (res >= r-l+1) {
                    break;
                }
            }
            printf("%d
", res);
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/wenruo/p/5562329.html