poj 3167 Cow Patterns (字符串__KMP好题)

题目链接: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]);
	}
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值