题目传送门
解题思路:
求左上角开始的最小矩阵可以延拓成整个图形,由于边界可以不完整延拓,所以想到kmp求循环节的思想。
我们先求一个ansc,使得每一行都可以通过前ansc个延拓完。
如果我们求出了一个ansc,那么我们我们就可以将每行前ansc个字符看做“一个字符”,然后进行列的kmp得出列的最小循环节ansr,最小矩阵就是ansc*ansr
显然当ansc最小时,ansr便能求得最小
那么如何求ansc?
是每行最小循环节的LCM(最小公倍数)吗?
不是。
看一下这组数据:
2 8
ABCDEFAB
AAABCAAA
这两行最小循环节为6,5,求得LCM>8,所以照我们所想ansc = 8,然而很明显看出ansc = 6
问题是什么呢?每一行不止一种循环节长度,而最小循环节的LCM不一定最小,我们实际需要做的是这样:
保存下每一行的所有可行循环节长度,每一行枚举一个长度,求所有行的LCM,但是这样做肯定不行,我们需要优化。
首先我们对于每行用于枚举的长度可以将某个倍数的长度看做一类,我们只需要枚举这里面最小的一个就可以,
比如5,10,15,20,我们只需枚举5,因为枚举其他几个求得的LCM只大不小。
好了,那我们现在的问题是:
1.如何求出每一行所有有效的数进行枚举(有效的数,这对解决第二个问题很关键)
2.以及如何求每行枚举不同数时的LCM
第二个问题的解决方法是:
开一个cnt[80]数组,每次枚举一个数,我们就把这个数以及这个数的倍数在cnt数组上标记,每一行的数枚举完毕后,我们
最后取cnt[i] = 行数的最小值即为ansc
比如我枚举这一行的循环节大小为3,那么我就标记cnt[3],cnt[6]...
这样最终标记次数 = 行数的最小的就是ansc了
第一个问题:
首先我们知道求出每行所有循环节的求法:
就是fail不断回溯,最长相同前后缀越来越短,求得的循环节越来越长
int now = fail[len];
int tot = 0;
while (now) cir[tot++] = len-now,now = fail[now];
当前循环节循环次数超过两次时,每次跳fail,新循环节长度 = 上一个长度 + 当前最小循环节(就是当前最小循环节的x倍)
过程中求得的都是无效的,当最后循环次数小于等于2时,我们对于此时循环缀余部分(或者循环次数刚好等于2时是一个完整循环节)单独抠出来重新求最小循环节,此时我们可以得到下一个有效循环节长度。有效是因为下一个循环节必定是某个倍数的最小循环节+一个比当前最小循环节小的数
举个例子:
因此我们怎么跳过中间两种无用的,直接取求第四种循环节长度呢?
就是直接用总长和当前有效循环节长度,下一个直接去找缀余就行了。
代码:
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define debug(x) printf("---- %s ----\n",#x)
#define pt(x) printf("%s = %d\n",#x,x)
const int N = 1e4+5;
char mp[N][80];
int fail[80];
int strfail[N];
int cnt[80];
int kmp(char *s,int len){
fail[0] = -1;
for (int i=0,j=-1;i<len;){
if (j==-1||s[i]==s[j]) i++,j++,fail[i] = j;
else j = fail[j];
}
return fail[len];
}
void mark(char *s,int len){
int cir = len - kmp(s,len),i;//i走出for循环-循环节长度,就等于缀余的起点
for (i = cir;i<len;i+=cir) cnt[i]++;
cnt[len]++;
i -= cir;
int f;//用于记录fail[len]
while (f = kmp(s+i,len-i)){
cir = len - f;
for (i = cir;i<len;i+=cir) cnt[i]++;
i -= cir;
}
}
int strkmp(int len,int l){
strfail[0] = -1;
for (int i=0,j=-1;i<len;){
if (j==-1||strncmp(mp[i],mp[j],l)==0) i++,j++,strfail[i] = j;
else j = strfail[j];
}
//for1(i,0,len) printf("strfail[%d]=%d\n",i,strfail[i]);
return len-strfail[len];
}
int main()
{
int r,c;
scanf("%d %d",&r,&c);
for0(i,0,r) scanf("%s",mp[i]);
for0(i,0,r) mark(mp[i],c);
int ansc,ansr;
for1(i,1,c) if (cnt[i]==r){ansc = i;break;}
//pt(ansc);
ansr = strkmp(r,ansc);
//pt(ansr);
printf("%d\n",ansc*ansr);
return 0;
}