奶牛矩阵
题目
解法
老哥,数据是真的水啊,让我的暴力过了就不说了,还花了我一个小时时间证明了题解里面一个AC的代码是错的!!!
首先,这些解法都基于一个特别基础的思想,就是这个最小覆盖矩阵的左上角一定在\((1,1)\)的位置,即使不在(又没说最小覆盖矩阵只能有一个),我们也可以移动到\((1,1)\)的位置(当然,这个时候这个最小覆盖矩阵就已经改变了)。
如:\(ABABA\)中\(BA\)是最小覆盖矩阵,那么往左移动,就变成了\(AB\),就是从\((1,1)\)开始的了。
同时最小覆盖矩阵可以分成行和列分开处理,这个是不难想到的,但是也是特别关键的思路。(一般矩阵不会就可以尝试分成行和列处理进行简单化处理)
解法1
暴力出奇迹!!!
我们用Hash来判断行和列是否相等,然后对行和列分别处理,对于行,每次代一个\(k\)进行\(pd\),如果存在\(i\)使得第\(i\)行不等于\(i-k\)行,那么\(pd\)返回\(false\),否则为\(true\),此时最小覆盖矩阵的行数出来了,就是\(k\),列也是同理。
时间复杂度:\(O(R^2+C^2)\),为什么能A?常数小,跑不满,重点是数据弱。
//自然溢出为模数
#include<cstdio>
#include<cstring>
#define N 11000
#define M 80
using namespace std;
typedef unsigned long long LL;
LL ha[N],hb[M],t=11;
int n,m;
char a[N][M];
bool pd(LL *b,int k,int li/*限制*/)
{
for(int i=k+1;i<=li;i++)
{
if(b[i]!=b[i-k])//不是这个数字
{
return 0;
}
}
return 1;
}
int find(LL *b,int li)
{
for(int i=1;i<li;i++)
{
if(pd(b,i,li)==1)
{
return i;
}
}
return li;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",a[i]+1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)ha[i]=ha[i]*t+(a[i][j]-'0'),hb[j]=hb[j]*t+(a[i][j]-'0');
}
printf("%d\n",find(ha,n)*find(hb,m));
return 0;
}
解法2(错误)
https://www.acwing.com/solution/content/3102/这个博主讲的,Hake数据已经发在评论里面了。
当然如果我理解错意思了就当我放了个屁。
这个主要是提醒一下,在一个不完全覆盖的字符串中补齐来找循环节,但是有没有考虑过有可能存在多个循环节相互冲突,补齐很难考虑到所有的循环节的,举个栗子:\(aabbbaaabbbaa\)中,\(aa\)和\(aa\)匹配,然后把\(bbbaaabbb\)补在了后面,但是这只是照顾到了循环节:\(aabbbaaabbb\),然而还有个更短的循环节\(aabbba\),这难道是启示我们补齐时要找最短的匹配吗?不,大概率不行。当然这里只是说明一下不能这么做罢了。
花了我一个小时去hake啊QAQ,还有代码也出了不少问题。
解法3
还有一种做法就是以kmp来解决行和列的覆盖。
不过把判断字符是否相等改成了判断行或列是否相等。
时间复杂度:\(O(RC)\)
这里主要利用了\(kmp\)中\(n-kmp[n]\)是循环节长度的性质(不管是不是精准覆盖。)
这里不认真证了,几个提示:\(kmp\)的定义是\(kmp[n]\)绝对是保证在最长的情况,同时可以证明\(n-kmp[n]\)确实是字符串中的一个循环节,而且如果是循环节,绝对可以满足\(kmp[n]\)中除了最大以外的其他定义,由最大也可以推得\(n-kmp[n]\)是最小的循环节,得证。(当然只是草草一证。)
代码采用https://www.acwing.com/video/100/评论中https://paste.ubuntu.com/p/kXNrbGcdhj/的
#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 80;
int n, m;
char str1[N][M];
char str2[M][N];
int ne1[N];
int ne2[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> str1[i];
}
int f=1,s=0;//一维 二维
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
str2[f++][s]=str1[i][j];
}
f=1;s++;
}
for (int j = 0, i = 2; i <= m; i ++ )
{
while (j && strcmp(str2[j + 1], str2[i])) j = ne2[j];
if (!strcmp(str2[j + 1], str2[i])) j ++ ;
ne2[i] = j;
}
int width = m - ne2[m];
for (int j = 0, i = 2; i <= n; i ++ )
{
while (j && strcmp(str1[j + 1], str1[i])) j = ne1[j];
if (!strcmp(str1[j + 1], str1[i])) j ++ ;
ne1[i] = j;
}
int height = n - ne1[n];
cout << width * height << endl;
return 0;
}
当然,把strcmp换成hash更快哟。