POJ 3167
题意:
原串长度为n,匹配串长度为m,输出匹配串在原串中出现几次,并且输出其出现的位置。
思路:
利用kmp的思想,求出Next数组,求的方法是对于数字的排列存在规律,因为两个数列不同只能比较相对大小,其体现在当前数字之前的比其小的个数和与之相等的个数,预处理其存在的个数。
as[i][j] : 在 a[i] 位置之前(包括本身)k数字存在的个数。
那么求出Next数组的时候利用前缀和后缀的思想,求出当前位置之前的数字比其小的个数和当前位置相等的个数。
在kmp算法中i和j移动的方法也是靠前缀和后缀满足相对大小,体现在比其小的个数和当前位置相等的个数。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 1e5+10;
int n,m,s;
int a[maxn],b[maxn];
int as[maxn][30],bs[maxn][30],Next[maxn];
vector<int>ans;
void init()
{
int i = 0,j;
while(i < n) {
if(i == 0) {
for(j = 1;j <= 25; j++) as[i][j] = 0;
}
else {
for(j = 1;j <= 25; j++) as[i][j] = as[i-1][j];
}
as[i][a[i]]++;
i++;
}
i = 0;
while(i < m) {
if(i == 0) {
for(j = 1;j <= 25; j++) bs[i][j] = 0;
}
else {
for(j = 1;j <= 25; j++) bs[i][j] = bs[i-1][j];
}
bs[i][b[i]]++;
i++;
}
}
void kmp_pre()
{
int i = 0,j;
j = Next[0] = -1;
while(i < m) {
int t11 = 0,t12 = 0,t21 = 0,t22 = 0;
for(int k = 1;k < b[i]; k++) {
if(i > j)
t11 += bs[i][k] - bs[i-j-1][k];
else t11 += bs[i][k];
}
if(i > j) t12 = bs[i][b[i]] - bs[i-j-1][b[i]];
else t12 = bs[i][b[i]];
for(int k = 1;k < b[j]; k++) {
t21 += bs[j][k];
}
if(j != -1) t22 = bs[j][b[j]];
if(j == -1 || (t11 == t21 && t12 == t22)) Next[++i] = ++j;
else j = Next[j];
}
}
void kmp()
{
int i = 0,j = 0;
while(i < n) {
int t11 = 0,t12 = 0,t21 = 0,t22 = 0;
for(int k = 1;k < a[i]; k++) {
if(i > j)
t11 += as[i][k] - as[i-j-1][k];
else t11 += as[i][k];
}
if(i > j) t12 = as[i][a[i]] - as[i-j-1][a[i]];
else t12 = as[i][a[i]];
for(int k = 1;k < b[j]; k++) {
t21 += bs[j][k];
}
t22 = bs[j][b[j]];
if(j == -1 || (t11 == t21 && t12 == t22)) {
i++,j++;
if(j >= m) {
ans.push_back(i-m+1);
j = Next[j];
}
}
else j = Next[j];
}
}
int main()
{
// freopen("in.txt","r",stdin);
while(scanf("%d%d%d",&n,&m,&s) != EOF) {
for(int i = 0;i < n; i++) scanf("%d",&a[i]);
for(int i = 0;i < m; i++) scanf("%d",&b[i]);
init();
kmp_pre();
kmp();
printf("%d\n",ans.size());
for(int i = 0;i < ans.size(); i++) {
printf("%d\n",ans[i]);
}
}
return 0;
}