题目大意:
给出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;
}