一道KMP的好题,可惜数据实在是太弱了。
给出R*C的字符矩阵,问至少多大面积的小矩阵可以覆盖掉整个大矩阵。很容易想到的解法就是求出循环节然后求最大公倍数,但是这样就忽视了可以不用正好覆盖这个条件,比如aaabaa可以拆成aaab/aa,aaaba/a,aaabaa,而不一定非要拆成4的倍数。正确的做法是求出每一列可能拆分的长度,然后取所有列都可以分的长度中最小的。比如
abcdeaa
aaabaaa
第一个串可以分的长度有5,7,第二个串可以分的长度有4,5,6,7,所以取5作为公共长度。至于怎么求一个字符串所有可以拆分的情况,用KMP处理一遍就可以了,然后从最后一列开始,取i=next[i]直到i=-1,这中间的i-next[i]值都是可取长度,比如aabaabaa,它的next数组是01012345,从最后一列开始,next[8]=5,有8-5=3可取,next[5]=2,有8-2=6可取,next[2]=1,有8-1=7可取,next[1]=0,有8-0=8可取,最终可取的数有3,6,7,8四个数。至于为什么这样做是可行的,想想next数组的性质就知道了,next[i]表示以i结尾的串的前缀和后缀的匹配程度。
算出宽度后只要将每行的前width个字符作为一个整体,然后KMP求出最小循环节作为height,求出乘积即可。
#include <stdio.h> #include <string.h> int r,c; char mz[10005][80]; int ctot[80],next[10005]; void kmp_next(char *s){ memset(next,0,5*c); next[1]=0,next[0]=-1; for(int i=2,j=0;i<=c;i++){ while(j>0&&s[j+1]!=s[i])j=next[j]; if(s[j+1]==s[i])j++; next[i]=j; } for(int i=next[c];i!=-1;i=next[i]){ ctot[c-i]++; } } int kmp_col(){ memset(next,0,sizeof next); next[1]=0; for(int i=2,j=0;i<=r;i++){ while(j>0&&strcmp(mz[j+1]+1,mz[i]+1))j=next[j]; if(strcmp(mz[j+1]+1,mz[i]+1)==0)j++; next[i]=j; } return r-next[r]; } int solve(){ memset(ctot,0,sizeof ctot); for(int i=1;i<=r;i++){ kmp_next(mz[i]); } int width=0; for(int i=1;i<=c;i++){ if(ctot[i]==r){ width=i; break; } } for(int i=1;i<=r;i++){ mz[i][width+1]='\0'; } return width*kmp_col(); } int main(){ while(scanf("%d%d",&r,&c)!=EOF){ for(int i=1;i<=r;i++){ scanf("%s",mz[i]+1); } int x=solve(); printf("%d\n",x); } return 0; }