每天早上,农夫约翰的奶牛们被挤奶的时候,都会站成一个 RR 行 CC 列的方阵。
现在在每个奶牛的身上标注表示其品种的大写字母,则所有奶牛共同构成了一个 RR 行 CC 列的字符矩阵。
现在给定由所有奶牛构成的矩阵,求它的最小覆盖子矩阵的面积是多少。
如果一个子矩阵无限复制扩张之后得到的矩阵能包含原来的矩阵,则称该子矩阵为覆盖子矩阵。
输入格式
第 11 行:输入两个用空格隔开的整数,RR 和 CC。
第 2..R+12..R+1 行:描绘由奶牛构成的 RR 行 CC 列的矩阵,每行 CC 个字符,字符之间没有空格。
输出格式
输出最小覆盖子矩阵的面积。(每个字符的面积为 11)
数据范围
1≤R≤100001≤R≤10000,
1≤C≤751≤C≤75输入样例:
2 5 ABABA ABABA
输出样例:
2
提示
样例中给出的矩阵的最小覆盖子矩阵为 ABAB,面积为 22。
1:枚举每行的宽度最小循环节;
{
1:若循环节可以完全精准覆盖则最小循环节是整个字符串长度的约数;
例如:ABCABCABCABC他的循环节可以是ABC(3), ABCABC(6), ABCABCABCABC(12)均是字符串长度的约数
}
2:将枚举的最小循环节看成一个整体比如循环节为ABC,将他看为一个整体A;
3:看为整体后计算每列的kmp的next数组;
{
性质:假设一个字符串A则len(A) - next[A]即为最小循环节的长度, len(A) /(len(A) - next[A])即为共有多少个循环节
(若想要证明可以看下这篇题解:
https://blog.csdn.net/qq_61935738/article/details/125676302);
}
4:最后每行中的最小宽度与每列中的最小循环节相乘即为答案:
问题:为何选择最小宽度?我选择一个较大宽度但是他的高度较低最后面积不是最小吗?
{
注释:next是kmp中的其含义是:最长后缀的最大前缀
证明:假设选择的宽度分别为a, b其中a < b若a即为每行最小循环节长度,
假设选择b为行的循环节长度,则在每列选择a的next匹配到的长度,肯定小于b的next匹配的长度,
因为在竖直方向上长度为a的next可以匹配, 但长度为b的next却不一定可以匹配,但是若是长度为b
的next可以匹配则长度为a的next也必然可以匹配
结论:若选择b则next将会变小n - next[b]将变大,所以因该选择宽度最小的a做竖直方向的kmp匹配;
}
#include <cstdio>
#include <string.h>
#include <iostream>
using namespace std;
const int N = 10010, M = 80;
int ne[N];//kmp中的next数组
bool st[M];//找出每行最小循环节的判断数组
char str[N][M];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ )//读入每行,枚举每行的最小循环节
{
scanf("%s", str[i]);
for (int j = 1; j <= m; j ++ ) //枚举每个循环节的长度
{
bool is_match = true;//判断第i行,j长度的字符串是否是循环节
for (int k = j; k < m; k += j) //枚举有多少个循环节
{
for (int u = 0; u < j && u + k < m; u ++ )//枚举在第k个区间中的循环节长度
{
if (str[i][u] != str[i][k + u])//说明不是循环节
{
is_match = false;
break;
}
if (!is_match) break;
}
if (!is_match) break;
}
if (!is_match) st[j] = true;//记录当前第i行长度为j的字符串不是循环节
}
}
int width;
for (int i = 1; i <= m; i ++ )
if (!st[i])//找出循环节最短的长度
{
width = i;
break;
}
for (int i = 1; i <= n; i ++ ) str[i][width] = '\0';//将每行的最小循环节看成一个整体,然后去做kmp,
//'\0'为字符串的结束标志
//若不知道kmp模板的可以看看这篇博客https://blog.csdn.net/qq_61935738/article/details/125689005
for (int i = 2, j = 0; i <= n; i ++ )//因为现在最短的循环节看成了一个整体,所以比较的是字符串
{
while (j && strcmp(str[j + 1], str[i])) j = ne[j];//strcmp若相等则返回0
if (!strcmp(str[j + 1], str[i])) j ++ ;
ne[i] = j;
}
int heigh = n - ne[n];//n - ne[n]为最短循环节的长度;
cout << width * heigh << endl;
return 0;
}