Match:Milking Grid(二维KMP算法)(POJ 2185)

                  

                  奶牛矩阵

  题目大意:给定一个矩阵,要你找到一个最小的矩阵,这个矩阵的无限扩充的矩阵包含着原来的矩阵

  思路:乍一看这一题确实很那做,因为我们不知道最小矩阵的位置,但是仔细一想,如果我们能把矩阵都放在左上角该多好,这样一来这一题好像又是循环数组那个样子了(二维的)。

  而事实上我们确实可以把所有情况都放在左上角,因为矩阵里面的元素的相对位置是不变的,这样一来我们就可以把矩阵看成都是一些元素从左上角往右下角扩充。那么现在问题就又回到了循环节的问题上了,我们可以把矩阵看成是很多很多个字符串组成,我们要找的就是一个最小的循环节的面积(一维的循环节是可以找长度,二维的循环节我们找面积)。

  那怎么找呢?既然是二维的,每一行和每一列都看成是一个新的“元素”(注意这里是以列和行为单位的,而不是以单个元素为单位,如果这题以单个元素为单位会出现严重的错误,这是网上很多AC代码的通病,我们先往下看)。一般的我们可以先这么想,我们可以先固定行,确定最小循环节的列数,然后以这个循环节的列数来找循环节的行数(把每一行都看成是新的元素),比如

    abab

    baba

    cdcd

    dcdc

  我们可以枚举每一行的循环节的长度(注意这里我用的是枚举),列举所有可能的循环情况,找到公共的最短的循环节的列数,比如例子里面所有行的公共的最短循环列数是2

  那么我们就可以在这个矩阵

    ab

    ba

    cd

    dc

  里面找到循环节的行数,把每一行都看成一个元素,那么这个最长的行数是4,最小循环节的面积是4。

  按照这个思路我们可以写出这样的代码

  

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <functional>
 4 #include <string.h>
 5 
 6 using namespace std;
 7 
 8 typedef char * _String;
 9 void SearchMatch(const int, _String);
10 void Get_Next(const int, const int);
11 
12 static char grid[10010][80], tmp[80];
13 static int _Next[10010], cir_match[10010];
14 
15 int main(void)//穷举法(500+ms)
16 {
17     int line, colum, min_newmatrix_col, i;
18     while (~scanf("%d%d", &line, &colum))
19     {
20         memset(cir_match, 0, sizeof(cir_match));
21         for (i = 0; i < line; i++)
22         {
23             scanf("%s", grid[i]);
24             strcpy(tmp, grid[i]);
25             SearchMatch(colum, grid[i]);
26         }
27         for (i = 0; i < colum; i++)
28             if (cir_match[i] == line)
29                 break;
30 
31         min_newmatrix_col = i;
32         Get_Next(line, min_newmatrix_col);
33         printf("%d
", min_newmatrix_col*(line - _Next[line]));
34         //colum - _Next[colum]就是关于行的循环节最小长度
35     }
36     return EXIT_SUCCESS;
37 }
38 
39 void SearchMatch(const int length, _String match_line)
40 {
41     int pos1, pos2;
42     //cir_match是统计每一行的非循环节的所有值,全部枚举找出最大的即可
43     for (int j = length - 1; j > 0; j--)
44     {
45         //枚举所有循环节长度
46         //理论上j==Length是不用统计的,因为如果不存在比len的最小循环节,那么最小的非循环节只能是长度为len
47         tmp[j] = '';
48         for (pos1 = 0, pos2 = 0; match_line[pos2] != ''; pos1++, pos2++)
49         {
50             if (tmp[pos1] == '')
51                 pos1 = 0;//到达指定循环节长度,返回循环节开始
52             if (tmp[pos1] != match_line[pos2])
53                 break;
54         }
55         if (match_line[pos2] == '')
56             cir_match[j]++;
57     }
58 }
59 
60 void Get_Next(const int line, const int new_col)
61 {
62     int i = 0, k = -1, j;
63     
64     for (j = 0; j < line; j++)
65         grid[j][new_col] = '';//锁定新的矩阵
66     _Next[0] = -1;
67 
68     while (i < line)
69     {
70         if (k == -1 || strcmp(grid[i], grid[k]) == 0)
71         {
72             i++;
73             k++;
74             _Next[i] = k;
75         }
76         else k = _Next[k];
77     }
78 }

  

  可见这样的代码的实现是正确的。

  我们这个时候来看一下网上很多AC代码的思路,他们的思路是把每一行的都用一次kmp,算出每一行的最短循环节,然后求他们的lcm(lcm超过原来的列数就把答案设置成最大的列数),对每一列也同样操作,最后的面积就是两个lcm的乘积

  但是这样的代码无法通过下面里的例子: 

    2 8

    ABCDEFAB

    AAAABAAA

  原因是出在行的计算上,对于第一行,计算出来的最大lcm是5,第二行是6,他们的lcm是30,取最大的列数8,对于列算出来的lcm是2,答案是16,显然是错的,因为这个的答案是12。

  其实原因很简单,因为第一行可行的循环不仅有5,还有678,但是kmp把678都忽略了,所以枚举可以解决这个问题。

  回到原问题来,显然枚举要500+ms太慢了,有没有更快的方法呢?显然是有的,那就是直接按照循环节的方法把每一列都看成是新的元素就好了

  

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <functional>
 4 #include <string.h>
 5 
 6 using namespace std;
 7 typedef char *_String;
 8 static char str[10010][80];
 9 static int _Next[10010];
10 
11 int Get_Next_Line(const int);
12 int Get_Next_Colum(const int, const int);
13 bool If_Colum_Match(const int, const int, const int);
14 
15 int main(void)//kmp双循环节算法(70+ms)
16 {
17     int line, colum, ans_h, ans_w;
18     while (~scanf("%d%d", &line, &colum))
19     {
20         for (int i = 0; i < line; i++)
21             scanf("%s", str[i]);
22         ans_h = Get_Next_Line(line);
23         ans_w = Get_Next_Colum(line, colum);
24         printf("%d
", ans_h*ans_w);
25     }
26     return EXIT_SUCCESS;
27 }
28 
29 int Get_Next_Line(const int line)
30 {
31     int i = 0, k = -1;
32     _Next[0] = -1;
33 
34     while (i < line)
35     {
36         if (k == -1 || strcmp(str[i], str[k]) == 0)
37         {
38             i++;
39             k++;
40             _Next[i] = k;
41         }
42         else k = _Next[k];
43     }
44     return line - _Next[line];
45 }
46 
47 int Get_Next_Colum(const int line, const int colum)
48 {
49     int i = 0, k = -1;
50     _Next[0] = -1;
51 
52     while (i < colum)
53     {
54         if (k == -1 || If_Colum_Match(line, i, k))
55         {
56             i++;
57             k++;
58             _Next[i] = k;
59         }
60         else k = _Next[k];
61     }
62     return colum - _Next[colum];
63 }
64 
65 bool If_Colum_Match(const int line_max, const int pos1, const int pos2)
66 {
67     for (int i = 0; i < line_max; i++)
68         if (str[i][pos1] != str[i][pos2])
69             return false;
70     return true;
71 }

 

  

  至此我们已经把二维循环节的问题解决了,这就是网上的所谓降阶法,其实也不是很难理解,一维循环节的问题请戳HDU 3746

  参考:http://blog.csdn.net/u013480600/article/details/22990715

     http://blog.sina.com.cn/s/blog_69c3f0410100tyjl.html

原文地址:https://www.cnblogs.com/Philip-Tell-Truth/p/5183309.html