KMP入门题集

这里是KMP的入门题集 主要运用的是next的一些简单性质 可以说是KMP裸题

在我的代码中 我使用的是整体右移法构建next

p指的是模式串

s指文本串

next 数组记录的就是下标所指字符串的最长相同前后缀

简单运用到的性质 i - next[i]就代表了最小循环节长度

第一题 友好的洛谷 (输出子串起始位置和next数组)

KMP字符串匹配 - 洛谷

#include <iostream>
#include <cstring>

using namespace std;
const int N = 1e6+10;
int _next[N];
char p[N], s[N];

void Getnext(int len)
{
	for(int i = 2,j = 0; i <= len; i++)
	{
		while( j && p[i] != p[j + 1])	j = _next[j];
		if(p[i] == p[j + 1] )	j++;
		_next[i] = j;
	}
}

void search(int start, int lens, int lenp)
{
	for(int i = start, j = 0; i <= lens; i++)
	{
		while( j && p[j + 1] != s[i])	j = _next[j];
		if( s[i] == p[j + 1])	j++;
		
		if(j == lenp)
		{
			printf("%d\n",i - lenp + 1);
		}
	}
}

int main()
{
	scanf("%s",s + 1);
	scanf("%s",p + 1);
	
	int lenp = strlen(p + 1);
	int lens = strlen(s + 1);
	
	Getnext(lenp);
	
	search(1, lens, lenp);
	
	for(int i = 1; i <= lenp; i++)	printf("%d ",_next[i]);//注意样例中的下标
	
	return 0;
	
}

寻找文本串中模式串个数

3461 -- Oulipo

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;
int nt[10010];
char p[10010];
char s[1000010];

void getnext(int lenp)
{
	memset(nt, 0, sizeof nt);
	for(int i = 2, j = 0; i <= lenp; i++)
	{
		while( j && p[i] != p[j + 1])	j = nt[j];
		if(p[i] == p[j + 1])	j++;
		nt[i] = j;
	}
}

int search(int st, int lenp, int lens)
{
	int cnt = 0;
	for(int i = 1, j = 0; i <= lens; i ++)
	{
		while( j && s[i] != p[j + 1])	j = nt[j];
		if(p[j + 1] == s[i])	j++;
		
		if(j == lenp)
		{
			j = nt[j];
			cnt ++;
		}	
	}
	return cnt;
}

int main()
{
	int n;
	scanf("%d",&n);
	while(n--)
	{
		scanf("%s",p + 1);
		scanf("%s",s + 1);
		int lenp = strlen(p + 1);
		int lens = strlen(s + 1);
		
		getnext(lenp);
		
		int ans = search(1, lenp, lens);
		
		printf("%d\n",ans);
	}
	return 0;
}

求文本串中循环节个数

2406 -- Power Strings

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
char p[1000010];
int nt[1000010];

void getnext(int lenp)
{
	memset(nt, 0, sizeof nt);
	
	for(int i = 2, j = 0; i <= lenp; i++)
	{
		while( j && p[i] != p[j + 1])	j = nt[j];
		if(p[i] == p[j + 1])	j++;
		nt[i] = j;
	}
}

int main()
{
	while(1)
	{
		memset(p, 0, sizeof p);
		
		scanf("%s", p + 1);
		
		if(p[1] == '.')	break;
		int lenp = strlen(p + 1);
//		cout << lenp << endl;
		
		getnext(lenp);
		
		int k = lenp - nt[lenp];
//		printf("%d\n",k);
		if(lenp % k == 0)	printf("%d\n",lenp / k);
		else printf("-1\n");
	}
	return 0;	
}

给一个字符串,问你前缀和后缀相同的位置有哪些

2752 -- Seek the Name, Seek the Fame

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;
const int N = 4e5 + 10;
char p[N];
int nt[N], ans[N];

void getnext(int lenp)
{
	for(int i = 2, j = 0; i <= lenp; i++)
	{
		while( j && p[i] != p[j + 1])	j = nt[j];
		if(p[i] == p[j + 1])	j++;
		nt[i] = j;
	}
}

void output(int len)
{
	for(int i = 0; i <= len; i++)	cout << p[i] << " ";
	cout << endl;
	
	for(int i = 0; i <= len; i++)	cout << nt[i] << " ";
	cout << endl;
}


int main()
{
	while(~scanf("%s", p + 1))
	{
		int lenp = strlen(p + 1), cnt = 0;
		getnext(lenp);
		
//		output(lenp);
		
		int last = nt[lenp];
		while( last != 0 )
		{
			ans[cnt++] = last;
			last = nt[last];
		}
		
		for(int i = cnt - 1; i >= 0; i--)
		{
			printf("%d ", ans[i]);
		}
		
		printf("%d\n",lenp);
	}
	return 0;
}


求出所有前缀中的循环节
141. 周期 - AcWing题库

#include <iostream>
#include <cstring>
#include <string>

using namespace std;
const int N = 1e6+10;
int nt[N];
int n, cnt;
char p[N];

void Getnext(int lenp)
{
	memset(nt, 0, sizeof nt);
	
	int j = 0;
	
	for(int i = 2; i <= lenp; i++)//从2开始 
	{
		while( j && p[i] != p[j + 1])	j = nt[j];
		if(p[i] == p[j + 1])	j++;
		nt[i] = j;
	}
}


int main()
{
	while(1)
	{
		cnt++;
		int lenp;
		if(cnt != 1)    puts("");
		
		scanf("%d",&lenp);
		if( lenp == 0)	break;
		
		scanf("%s",p + 1);
		
		//Get next[
		Getnext(lenp + 1); 
		
		printf("Test case #%d\n",cnt);
		
		for(int i = 1; i <= lenp; i++)
		{
			int t = i - nt[i];
			if( i % t == 0 && i / t > 1)
			{
				printf("%d %d\n", i, i / t);
			}
		}
	}
	
	return 0;	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值