poj2185 kmp经典好题!

题意:先对于一个n*m的字符矩阵S,求一个S中最小的矩阵t,使得这个矩阵可以通过复制,组成一个大矩阵T,T可以完全覆盖S。注意,这里S和T不必要相等,只要T中和S重合的那部分完全等于S即可。

思路:网上通行的题解方案其实是不可取的,能AC.....只能说数据比较弱。这道题很多题解说求每行最小重复子串长度的最小公倍数,每列最小重复子串长度的最小公倍数。其实这是很容易看出反例的。因为并不要求矩阵T和S相等,而是包含关系,右下方是可以多余的。

例:aaabaa   

可以用 aaab 复制两次得到 aaabaaab 来覆盖 aaabaa。也可以用aaaba 复制两次得到aaabaaaaba 来覆盖aaabaa。前一种是最小方案。

 

这道题理解了kmp中的next指针(程序中指fail)也就不难了,建议黄超神牛的kmp详解http://blog.csdn.net/my_gemini_acm/article/details/8248183对于长度为m的一个串s[i],显然m-next[m],m-next[next[m]],...... 都是能通过复制,完全覆盖字符串的可行串,用kmp得到所有可行的方案。对所有s[i]都可行的最小的划分方案即为最终的宽度w。至于矩阵的高度h,我们只需要把每个s[i] 的前w个字符当做一个整体,记作W[i] ,对W[1],W[2],...... 作kmp,得到竖着的最小重复“子串”,即为高h。

例:aaabaaa

    aaaabaa

    aaabaaa

    aaaabaa

  对s[1] 可行划分为aaab 、aaaba 、aaabaa 、aaabaaa ,即4 5 6 7

  对s[2] 可行划分为aaaab 、aaaaba 、aaaabaa ,即5 6 7

  对s[3] 可行划分为aaab 、aaaba 、aaabaa 、aaabaaa ,即4 5 6 7

  对s[4] 可行划分为aaaab 、aaaaba 、aaaabaa ,即5 6 7

  我们取最小的w=5,得到W[1]=aaaba,W[2]=aaaab ,W[3]=aaaba ,W[4]=aaaaba

  继续做kmp,得到W的最小重复“子串”为W[1]W[2],即h=2。

  故而最终结果面积为w*h

 

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

const int maxn=10011;

int fail[maxn];
int num[100];
char s[10001][100];

void kmp_1(char a[100])
{
	int i,j;      
	j=-1;
	memset(fail,-1,sizeof(fail));
	for(i=1;i<strlen(a);i++)
	{
		while(j>-1 && a[j+1]!=a[i]) j=fail[j];
		if(a[j+1]==a[i]) j++;
		fail[i]=j;
	}      
	return ;		
}

void kmp_2(int n)
{
	int j=0;
	memset(fail,0,sizeof(fail));
	for(int i=2;i<=n;i++)
	{
		while(j>0 && (strcmp(s[j+1],s[i])!=0))
		    j=fail[j];
		if(strcmp(s[j+1],s[i])==0) j++;
		fail[i]=j;
	}
	return ;
}
		
void work(char a[100])
{
	int n=strlen(a)-1;
	int j=n;
	while(j>=0)
	{
		num[n-fail[j]]++;
		j=fail[j];
	}
	return ;
}	
	
int main()
{
	int N,M;
	
	while(cin>>N>>M)
	{
		memset(num,0,sizeof(num));
		for(int i=1;i<=N;i++)
		{
			scanf("%s",s[i]);   //担心cin会超时....
			kmp_1(s[i]);
			work(s[i]);
		}
		bool flag=0;
		int w,h;		
		for(int i=1;i<=M;i++)
			if(num[i]==N) 
			{
				w=i;
				flag=1;
				break;
			}
		kmp_2(N);
		h=N-fail[N];	    
		cout<<w*h<<endl;
	}		
	return 0;
}
		
		
	

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值