poj 3167 KMP好题

题目大意:

给出A,B两个序列(|A|≤100,000,|B|≤25,000),序列中元素的大小1≤S≤25,然后在A里面找出满足B特征的子序列(当然子序列和B的长度要相等),输出所有A中满足条件的子序列的开端下标。如样例:

A 5 6 2 10 10 7 3 2 9

B 1 4 4 3 2 1

不妨用#i表示B序列的第i数,则#1和#6在B序列中是相等且是最小的,所以在满足条件的子序列中,第1位和第6位的元素也要是相等且是最小的数。同样,#5是第二小且没有和它相同的数,那么满足条件的子序列的第5位也要是第二小,且没有和它相同的数,以此类推。

A 5 6 (2 10 10 7 3 2) 9

B      14  4 3 2 1

A序列中用括号括起来的子序列就满足B的特征。

普通的KMP难以解决问题,所以我们要对算法进行一些改进。改进的重点是如何判断匹配时新的元素是否能满足条件。

A

 5     

 6      

2 (i)

10    

10    

7(i+j)

3      

2      

9      

B

 

 

1 (0)

4

4

3  (j)

2

1

 

我们可以从匹配的子序列和B的特征下手。

不妨设匹配的子序列的开端下标为i,B的开端下标为0。那么,对于匹配到的第j个元素,有以下三个特征:

在A[i..i+j-1]中,比A[i+j]小的元素的个数和在B[0..j-1]中,比B[j]小的元素个数相等;(观察表格中蓝色部分,比7小的元素有2一个,同样,比3小的元素有1一个。)

在A[i..i+j-1]中,等于A[i+j]的元素的个数和在B[0..j-1]中,等于B[j]的元素个数相等;

在A[i..i+j-1]中,比A[i+j]大的元素的个数和在B[0..j-1]中,比B[j]大的元素个数相等。

我们就可以利用这个特征来解决问题。简单来说,就是将普通KMP中A[i]是否等于B[j],的判断改为上述特征的判断。由于元素的大小不超过25,所以直接开一个count[]数组,来统计个数,如果元素的大小比较大的话,可以用树状数组一类的数据结构来优化。

对于这样一个序列,count[]为:

1 4 2 3 4 2

Count 1 2 3 4 5 6

      1 2 1 2 0 0

若求比4小的个数,则求count[1]+count[2]+count[3]。

当然,count数组是变化的,也就是说,要随时进行添加和删除。

普通KMP的时间复杂度是O(|A|+|B|),那么加上个数统计,就要O((|A|+|B|)×S),用树状数组优化就是O((|A|+|B|)×log S)。同时,由于三个特征数量统计的总和是一定的,也就是说当其中两个特征满足时,第三个也同样满足。我们可以只判断前两个特征是否满足就可以了。

对于next[]数组,求法类似。

那么,KMP的作用就不单单是用来寻找子串了。还可以用来寻找具有同样特征的字串。 


#include <cstdio>
#include <cstring>
using namespace std;

#define maxn 100007   //A数组大小
#define maxk 25007    //B数组大小
#define maxs 27       //元素个数较小

int n, m;
int A[maxn], B[maxk];
int m1[maxk], m2[maxk];
int cnt[maxs];
int next[maxk];

int tot, ans[maxn];

inline int Sum(int i) {
    int res = 0;
    for (; i > 0; i --) res += cnt[i];   //统计比 i 小的元素个数
    return res;
}

int main() {
    freopen("cpattern.in", "r", stdin);
    freopen("cpattern.out", "w", stdout);
    
    scanf("%d%d%*d\n", &n, &m);   //S的大小可以忽略
    for (int i = 0; i < n; i ++) scanf("%d", &A[i]);    //读入A数组
    for (int i = 0; i < m; i ++) {
        scanf("%d", &B[i]);        //读入B数组
        m1[i] = Sum(B[i] - 1);     //统计特征1:比B[i]小的元素个数
        m2[i] = cnt[B[i]];         //统计特征2:和B[i]相等的元素个数
        cnt[B[i]] ++;              //把B[i]加入count[]
    }
    
    memset(cnt, 0, sizeof(cnt));  //清空count[]
    
    int j = -1;
    next[0] = -1;                  //同普通KMP,这里用0表示前后能匹配1个
    for (int i = 1 ; i < m; i ++) {
        while (j >= 0 && (Sum(B[i]-1) != m1[j+1] || cnt[B[i]] != m2[j+1])) {//两个特征的判断
            for (int k = i-j-1; k < i-next[j]-1; k ++) cnt[B[k]] --;        //匹配失败时要把前面的删除
            j = next[j];
        }
        if (Sum(B[i]-1) == m1[j+1] && cnt[B[i]] == m2[j+1]) {
            j ++;
            cnt[B[i]] ++;     //如果匹配失败则不加入count[]
        }
        next[i] = j;
    }
    
    memset(cnt, 0, sizeof(cnt));  //清空count[]
    
    j = -1;
    for (int i = 0; i < n; i ++) {
        while (j >= 0 && (Sum(A[i]-1) != m1[j+1] || cnt[A[i]] != m2[j+1])) {//同上
            for (int k = i-j-1; k < i-next[j]-1; k ++) cnt[A[k]] --;
            j = next[j];
        }
        if (Sum(A[i]-1) == m1[j+1] && cnt[A[i]] == m2[j+1]) {               //同上
            j ++;
            cnt[A[i]] ++;
        }
        if (j == m - 1) {           //匹配成功
            ans[tot] = i - j + 1;   //加入答案,+1是下标的调整
            tot ++;
            
            for (int k = i-j; k < i-next[j]; k ++) cnt[A[k]] --;//这里要注意,当前位置还是i,所以不用-1
            j = next[j];
        }
    }
    
    printf("%d\n", tot);      //输出
    for (int i = 0; i < tot; i ++) printf("%d\n", ans[i]);
    
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值