HihoCoder第三周与POJ2406:KMP算法总结

HihoCoder第三周:

输入

第一行一个整数N,表示测试数据组数。

接下来的N*2行,每两行表示一个测试数据。在每一个测试数据中,第一行为模式串,由不超过10^4个大写字母组成,第二行为原串,由不超过10^6个大写字母组成。

其中N<=20

输出

对于每一个测试数据,按照它们在输入中出现的顺序输出一行Ans,表示模式串在原串中出现的次数。

样例输入

5

HA

HAHAHA

WQN

WQN

ADA

ADADADA

BABABB

BABABABABABABABABB

DAD

ADDAADAADDAAADAAD

样例输出

3

1

3

1

0

 

KMP于我的理解就是通过记录已经匹配的字符的情况,防止不必要的匹配。记录已经匹配字符的单元实际就是next数组,我其实觉得想法非常巧妙的一点在于,模式串与原串匹配,正常来讲总是会想跟原串或许会有什么关系,因为要记录已经匹配的字符嘛。但是相反,next只是跟模式串有关,原理就在于

如果一个字符串满足这个条件:有一个整数k,使得

p0 p1 ...pk-1 pk = pj-k pj-k+1...pj-1 pj

这样我下一次匹配不成功的时候,原串中的j就不用回溯,模式串中的i=next[i],因为你本身是满足这个条件的,所以当moshi[i]不相等的时候,我就不用从第0个字符从头开始试是否相等,而只需从moshi[next[i]]开始匹配即可。

next数组就是记录这个字符之前有多少字符和整个字符串最开始的相应的字符相等的。

之后就是如何求next数组。

求的时候本身就可以用递归来求:

如果moshi[i]==moshi[j]

那么i++;

j++;

moshi[j]= i;这里i代表从头开始的前缀,j代表后缀

如果不相等

那么i=next[i];

代码:

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

char moshi[10004],yuanchuan[1000004];
int calnext[1000004];

void Calnext(int len1)
{
	memset(calnext,0,1000004*sizeof(int));

	calnext[0]=-1;

	int i,j=-1;

	for(i=0;i<len1+1;)
	{
		if(moshi[i]==moshi[j]||j==-1)
		{
			i++;
			j++;
			if(moshi[i]==moshi[j])//calnext[i]==calnext[j]
			{
				calnext[i]=calnext[j];
			}
			else
			{
				calnext[i]=j;
			}
		}
		else
		{
			j=calnext[j];
		}
	}
}

int Cal(int len1,int len2)
{
	int result=0;
	int i=0,j=0;
//仔细想一下,j其实是一直往前走的!!!!!它没有回去的时候!!!!
	while(i<len1&&j<len2)
	{
		if(moshi[i]==yuanchuan[j])
		{
			i++;
			j++;
		}
		else
		{
			if(calnext[i]==-1)
			{
				i=0;
				j++;
			}
			else
			{
				i=calnext[i];
			}
		}
		if(i>=len1)
		{
			result++;
			i=calnext[len1];
		}
	}
	return result;
}

int main()
{
	int count;
	cin>>count;

	while(count--)
	{
		cin>>moshi;
		cin>>yuanchuan;

		int len1 = strlen(moshi);
		int len2 = strlen(yuanchuan);

		Calnext(len1);

		cout<<Cal(len1,len2)<<endl;

	}

	return 0;
}

之前遇到的两个问题都在注释中了,一个是改进求解next数组时,应该是判断moshi是否相等,而跟next没什么关系。

比方说在匹配abc,c处错误,可能就跳到之前abd处,你只需判断c和d是否相等,如果相等了,说明这里不用再记录d的位置了,记录d如果不相等时会跳到哪里吧,可能是再之前的abf处。如果按照我之前的想法简直就解释不了了,abc,匹配c时出问题,跳转到abd处,你判断d可能会跳到f处,这里,然后你把c也弄到f哪里根本解释不同。(这里仅仅为我错误又很缥缈的思路。。。。)

第二个就是我受了暴力搜索的影响,其实看别人写的暴力,也是就一个循环,原串中的j也是不用回溯的,用一个k表示就好,不相等的话k置成0即可。我还弄了两层循环,好不麻烦。结果就是写了next数组之后还是两层循环,还是timelimited。真是。。。。在kmp算法中,j只是会一味的往前走,不能回头。所以一层循环,所以时间复杂度就是O(m+n)。

POJ2406:

Description

Given two strings a and b we define a*b to be theirconcatenation. For example, if a = "abc" and b = "def" thena*b = "abcdef". If we think of concatenation as multiplication,exponentiation by a non-negative integer is defined in the normal way: a^0 ="" (the empty string) and a^(n+1) = a*(a^n).

Input

Each test case is a line of input representing s, a string ofprintable characters. The length of s will be at least 1 and will not exceed 1million characters. A line containing a period follows the last test case.

Output

For each s you should print the largest n such that s = a^n forsome string a.

Sample Input

abcd

aaaa

ababab

.

Sample Output

1

4

3

 

代码:

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

char moshi[1000004];
int calnext[1000004];

void Calnext(int len1)
{
	memset(calnext,0,1000004*sizeof(int));

	calnext[0]=-1;

	int i,j=-1;

	for(i=0;i<len1;)
	{
		if(moshi[i]==moshi[j]||j==-1)
		{
			i++;
			j++;
			calnext[i]=j;
		}
		else
		{
			j=calnext[j];
		}
	}
}


int main()
{
	while(true)
	{
		scanf("%s",moshi);

		if(strcmp(moshi, "."))
		{
			int len1 = strlen(moshi);

			Calnext(len1);

			if(len1%(len1-calnext[len1])==0)
			{
				cout<<len1/(len1-calnext[len1])<<endl;
			}
			else
			{
				cout<<'1'<<endl;
			}

		}
		else
			break;

	}

	system("pause");
	return 0;
}

POJ2406这道题有意思的地方在于求next[len1],想一想就是abcabc#,如果#这里不匹配的话会跳转到a那里,即是位置3。如果是abdab#,会跳转到d那里,即是位置2。

所以我的理解就是当next[len1]这个值如果大于等于一半的len1的话,那么这个字符串就是powerstring。而如何把单元重复的长度求出来,就是len1-next[len1](试想一下abcabcabcabc#这样的字符串,它会记录的是最大的k是9,即前缀和后缀重叠了的是9,所以未重叠部分其实就是单位的abc,所以有多少个abc呢,结果就是 除以就好了。)

小弟弱傻痴菜,还望路人多多指正,互相交流。


版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://www.cnblogs.com/lightspeedsmallson/p/4785912.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值