KMP的next数组和循环节问题的应用2:奶牛矩阵

题目链接:https://www.acwing.com/problem/content/description/161/

题目:

每天早上,农夫约翰的奶牛们被挤奶的时候,都会站成一个 R 行 C 列的方阵。

现在在每个奶牛的身上标注表示其品种的大写字母,则所有奶牛共同构成了一个 R 行 C 列的字符矩阵。

现在给定由所有奶牛构成的矩阵,求它的最小覆盖子矩阵的面积是多少。

如果一个子矩阵无限复制扩张之后得到的矩阵能包含原来的矩阵,则称该子矩阵为覆盖子矩阵。

输入格式

第 1 行:输入两个用空格隔开的整数,R 和 C。

第 2..R+1 行:描绘由奶牛构成的 R 行 C 列的矩阵,每行 C 个字符,字符之间没有空格。

输出格式

输出最小覆盖子矩阵的面积。(每个字符的面积为 1)

数据范围

1≤R≤10000
1≤C≤75

输入样例:

2 5
ABABA
ABABA

输出样例:

2

提示

样例中给出的矩阵的最小覆盖子矩阵为 AB,面积为 2。

 分析:

首先对行进行处理。通过某一个最小的矩阵能够组成这么一组给出的矩阵的字符串情况。

说明,题目给出的字符串可以通过某一循环的字符串所组成。所以可以知道与循环节有关。

每一行的字符串都会有一个循环节,此循环节可以组成此行字符串。

那么问题来了?此循环节的长度应该怎么求呢?

每一行的字符串都会有一个最小的循环节,i - ne[i]

但每一行的循环节长度未必是相同的。

同时:循环节有一个需要了解的知识:

1.如果是一个完美的循环字符串的话,则所有循环节的长度都是最短循环节长度的整数倍。

如果每一行都是完美循环字符串的话,则可以求出每一行字符串的最短循环节长度,x1,x2,x3...然后求出这x1,x2,x3..的最小公倍数,则就是满足所有行的循环节长度了。

如:ABCABCABCABC(最小循环节为3)

        DEFGHHDEFGHH(最小循环节为6,就可以求出最小公倍数的话为6)

就可以使用

                  ABCABC

                  DEFGHH

进行完全覆盖.

但是很可惜,有可能出现这样的情况:

如:AAABAA(最小循环节为4,循环节也可以为5,6)

       ABCDEA (最小循环节为5,,循环节也可以为6)

则这两行的循环节结果就是5.

这是因为另一个循环节有关知识:就是不完美循环字符串的所有循环节长度就不一定是最短循环节长度的整数倍,只有完美循环字符串的所有循环节长度是最短循环节长度的整数倍。如:AAABAA:最小循环节为4,但循环节也可以为5,6.

所以方式1求公倍数的话则对此情况不成立。不是最小的宽度的情况。

 正确解决宽度值的方法:

所以此题的方式是使用: for()循环遍历所有可能的循环节长度,从1~c.判断哪一个循环节长度满足。因为每一行的字符串的长度不是很大,最长只为75。

时间复杂度为75 * 75 * 10000

求解高度的方法:

行数最多为 10000,所以不可能使用求宽度这样的方式,可以使用KMP的next数组,求出最小循环节的长度。r - ne[r]。

如何求出所需要的next[]数组呢?

根据题意可以知道这是一个矩阵。也就是每一行的字符串长度都是一样的。而宽度我们已经求出,所以每一行的值都是这些宽度对应的字符串循环组成的结果。

所以每两行要么就是相同的字符串要么就是不同的字符串。

于是我们将每一行字符串都看作是一个字符,所有行组成一个字符串。求这个字符串的最小循环节长度。

如宽度已经求出最小为3:

ABCABCABC

CDECDECDE

ABDABDABD

ABCABCABC

CDECDECDE

ABDABDABD

行的循环节为3已经知道了。

现在求列的循环节,

第一行的ABC 与 第四行的ABC是相同的,

第二行的CDE 与 第五行的CDE是相同的。

第三行的ABD 与 第三行的ABD是相同的。

从而实际上可以看作什么呢?

A

B

C

A

B

C

转为一般所看见的字符串ABCABC求最小循环节长度。

 综上:

宽度使用for()进行遍历,找到满足所有行字符串的循环节最小长度

高 则将每一行看成一个字符,如每一行使用string进行存储,则每一行都可以进行比较看是否相等, 然后使用KMP的next数组,找到最小循环节r - ne[r]。

问题:

这样的做法是将宽和高分离开来做,宽和高是否独立呢? 也就是是否宽最小的时候,高的值也最小呢?

不会出现宽大一些,高变的小一些情况嘛? 

 答:

实际上宽与高是独立的。 我们所使用的宽为可以组成所有行的最小循环节长度。  比如所求宽度为3, 现在有一个循环节长度为5.  5满足条件的高,实际上宽度为3的情况也可以满足。

所以二者实际上是独立的。

代码实现:

# include <iostream>
# include <string>
# include <algorithm>
# include <cstring>
using namespace std;

const int R = 10010 ,C = 80;

string ch[R];
bool flag[C];
int ne[R];

int r,c;

/*
1.为什么行的最小循环节不能用 每一行的最小循环节的公倍数代替呢?

因为每一行的字符串不一定都是完美循环节字符串,所以公倍数求出的循环节长度虽然满足是每一行的循环节,但未必就满足最小循环节的情况。也就是行的宽度未必是最小值

2.为什么行的最小循环节不能用 每一行的最小循环节的最大值代替呢?

而这个是考虑到了未必是完美循环节的情况。
但是如果不是完美循环节的话,那么这个最大值未必满足所有行的字符串循环节的条件。所以也不成立。
*/

int main()
{
    scanf("%d %d",&r,&c);
    for(int i = 1 ; i <= r ; i++)
    {
        cin >> ch[i];
    }
    
    //求行的最小循环节
    memset(flag,true,sizeof flag);
    
    for(int i = 1 ; i <= r ; i++)  //判断每一行
    {
        for(int j = 1 ; j <= c ; j++) //循环节长度最小可能为1,最大可能为65
        {
            //printf("长度为:%d\n",j);
            if(flag[j])
            {
                for(int k = 0 ; k < j ; k++)
                {
                    for(int t = 1 ; t * j + k < c ; t++)
                    {
                        /*
                        if(t * j + k == 5)
                        {
                            printf("5的时候的情况:%d %d %d\n",t,j,k);
                        }  越界是因为存在不完美循环节的情况。*/
                        
                        //printf("ch[%d][%d] 与 ch[%d][%d]进行比较\n",i,t * j + k,i,k);
                        if(ch[i][t * j + k] != ch[i][k])
                        {
                            flag[j] = false;
                            break;
                        }
                    }
                    if(flag[j] == false)
                    {
                        break;
                    }
                }
            }
        }
    }
    
    int width = 0;  //宽度
    for(int j = 1 ; j <= c ; j++)
    {
        if(flag[j] == true)
        {
            width = j;
            break;
        }
    }
    for(int i = 2, j = 0 ; i <= r; i++)
    {
        while(j && ch[i] != ch[j + 1])
        {
            j = ne[j];
        }
        if(ch[i] == ch[j + 1])  //如果字符串ch[i]等于字符串 ch[j]时
        {
            j++;
        }
        ne[i] = j;
    }
    int gao = r - ne[r];
    printf("%d\n",gao * width);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值