题意:先对于一个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;
}