《算法竞赛进阶指南》奶牛矩阵

每天早上,农夫约翰的奶牛们被挤奶的时候,都会站成一个 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;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值