题目链接:http://poj.org/problem?id=3167
题目大意:给你一个数字序列arr,再给一个数字序列brr,arr和brr中的数字都是1到s(s<=25)。每个序列里的数字都有个排名,也就是第几小,现在我们要用arr来匹配brr,在本题中匹配的意思,每个数字的排名都一样,即同是第k小,最后输出匹配的所有位置,一行一个位置。这题是KMP好题,做完你对KMP算法理解得就会十分透彻。
解题思路:一开始题目没看懂,题目真心难懂,但看了测试数据后就恍然大悟知道是匹配题。要解这题关键在与几个地方,能快速地算出当个数字的排名,next数组也很关键和平时的不同。 这题我在两个地方都纠结过。现在我约定brr算一个区间,匹配的时候arr也会有个区间。我最开始算arr中每个数字的排名是用区间末出现过的数字减去区间首的数字来判断区间中比当个数字小的数字,就是排名,但这是错的。要算出比这个小的还要算出和这个数字相等的,这样就准确的算出排名了。关于next数组,我最开始以为像普通kmp那样算就好,但是也是错的,比如1 2 3 4 5,我算出来的next数组是-1,0,0,0,0,但实际应该是-1,0,1,2,3,在brr[4]失配时应该回到 brr[3]匹配的。改了这两个地方以后就顺利ac了。
测试数据:
本体是USACO2005的题目,有官方测试数据,如果觉得麻烦可以发发件给我,我这里有官方测试数据。
再附一些我自己想的测试数据
1 6 10
1
1 1 1 2 1 1
10 5 10
10 8 7 6 5 4 3 2 1 9
5 4 3 2 1
5 3 6
1 5 3 6 1
2 3 1
10 5 10
1 2 3 4 5 6 7 8 9 10
1 2 4 5 7
3 3 10
1 2 3
4 5 6
5 2 10
1 1 1 1 1
1 1
3 3 10
3 2 1
9 5 2
4 1 10
1 2 3 4
6
4 5 10
1 2 3 4
1 2 3 4 5
5 3 10
1 2 1 2 1
1 2 1
5 3 10
1 2 1 2 1
1 4 2
5 3 10
1 2 1 2 1
4 2 1
5 3 10
1 2 1 2 1
2 4 1
5 3 10
1 2 1 2 1
2 1 4
4 4 10
1 1 1 1
4 3 2 1
4 4 10
4 3 2 1
1 1 1 1
4 4 10
1 1 1 1
1 2 3 4
4 4 10
1 2 3 4
1 1 1 1
4 4 10
1 2 3 4
4 3 2 1
4 4 10
4 3 2 1
1 2 3 4
4 2 10
1 1 1 1
1 2
4 2 10
1 1 1 1
2 1
代码:
#include <stdio.h> #include <string.h> #define MAX 100010 #define MIN 30000 int n,m,s,smalli,smallj,equi,equj; int tot,pos[MAX],next[MIN]; //匹配到的位置和总数和模式串的next数组 int arr[MAX],match[MIN]; //arr为当前的数字,match为模式串 int low[MIN][30],vis[MAX][30]; //low[i]表示在i位置及以前比match[i]小的数,vis相同 void CountLow() { int i,j,k; memset(low,0,sizeof(low)); memset(vis,0,sizeof(vis)); vis[0][arr[0]] = 1; low[0][match[0]] = 1; for (i = 1; i < n; ++i) { for (j = 1; j <= s; ++j) vis[i][j] = vis[i-1][j]; vis[i][arr[i]]++; } for (i = 1; i < m; ++i) { for (j = 1; j <= s; ++j) low[i][j] = low[i-1][j]; low[i][match[i]]++; } } void GetNext() { int i,j,k,s; i = 0,j = -1; next[0] = -1; while (i < m) { smalli = smallj = 0; for (s = 1; s < match[i]; ++s) smalli += low[i][s] - low[i-j-1][s]; //区间内比arr[i]的数的个数 equi = low[i][match[i]] - low[i-j-1][match[i]]; //区间内arr[i]的个数 for (s = 1;s < match[j]; ++s) //同上 smallj += low[j][s]; equj = low[j][match[j]]; if (j == -1 || smalli == smallj && equi == equj) i++,j++,next[i] = j; else j = next[j]; } } void MatchKMP() { int i,j,k,tp,s,tp2; i = j = 0; while (i < n && j < m) { smalli = smallj = 0; if (i > j) { for (s = 1; s < arr[i]; ++s) smalli += vis[i][s] - vis[i-j-1][s]; //区间内比arr[i]的数的个数 equi = vis[i][arr[i]] - vis[i-j-1][arr[i]]; //区间内arr[i]的个数 } else { for (s = 1;s < arr[i]; ++s) smalli += vis[i][s]; equi = vis[i][arr[i]]; } for (s = 1; s < match[j]; ++s) //同上 smallj += low[j][s]; equj = low[j][match[j]]; if (j == -1 || smalli == smallj && equi == equj) i++,j++; else j = next[j]; if (j == m) { tot++,pos[tot] = i - m + 1; j = next[j]; } } } int main() { int i,j,k; while (scanf("%d%d%d",&n,&m,&s) != EOF) { for (i = 0; i < n; ++i) scanf("%d",&arr[i]); for (i = 0; i < m; ++i) scanf("%d",&match[i]); tot = 0; CountLow(); GetNext(); MatchKMP(); printf("%d\n",tot); for (i = 1; i <= tot; ++i) printf("%d\n",pos[i]); } }