NKOJ3807 卜拉美古塔与最小表示法
时间限制 : - MS 空间限制 : 165536 KB
评测说明 : 1000ms
问题描述
/*
婆罗摩笈多先生在中国也十分著名,并且更加著名的是一个以他的名字命名的定理:
若圆内接四边形ABCD的对角线AC⊥BD,垂足为M。EF⊥BC,且M在EF上。那么F是AD的中点。
但鲜有人知道,相较于婆罗摩笈多,卜拉美古塔是一个更加富有艺术感的译名。这个译名使他的他的定理听起来更加优美——卜拉美古塔定理。
不管怎样,这个定理做出的卓越贡献使他在天堂中获得了一个珍贵的储存空间高达12.8M的芯片储存器(天堂中稀缺的Si元素都被何老板制造私人跳棋库了,导致天堂中的Si资源极度缺乏)。
正巧,他获得了一个由A~E这五个大写英文字母组成的密码字符矩阵,他想要把它存进芯片储存器中来分析它,但这样芯片剩余的空间就会少得可怜,这可让他操碎了心。
*/
幸好,由ZMY先生改进的万能算法“最小表示法”就在此时因惊动了上帝而从人间传入天堂。于是卜拉美古塔先生对这个算法产生了浓厚的兴趣,并试图将其推广到矩阵之上。
定义一n行m列的字符矩阵C的最小表示法为满足以下条件的r行c列的子矩阵D:
1.满足对于C矩阵的任意元素C[i][j],C[i][j]=D[i%r][j%c](编号从0开始)。
2.是满足条件1的矩阵中r*c最小的。
3.是满足条件2的矩阵中r+c最小的。
4.是满足条件3的矩阵中r最小的。
这样只需记录n,m和最小表示法便可以轻易复原原矩阵,所需的储存空间大大减小。
但现在卜拉美古塔先生突然不想对其推广了,现在他将把这个n*m的字符矩阵给你,请你帮他找出这个矩阵的最小表示法。
输入格式
若干行,每行包括矩阵的一行(保证每行均有m个字符)。
输出格式
第一行,空格间隔的两个整数n,m。
第二行及以后,输出矩阵的最小表示法。
样例输入
ADABADA
ACBBACB
样例输出
2 7
ADAB
ACBB
提示
对于30%的数据,1<=n<=3,1<=m<=15。
对于50%的数据,1<=n<=600,1<=m<=600。
对于100%的数据,1<=n<=3000,1<=m<=3000。
来源 he47
思路:将一整行、一整列看做一个字符,分别对行、列求最小循环节。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
char a[3003][3003];
int n,m;
int fail[3003];
bool check_m(int x,int y)
{
for(int i=0;i<n;i++) if(a[i][x]!=a[i][y]) return false;
return true;
}
int kmp_m()
{
int j;
fail[0]=j=-1;
for(int i=1;i<m;i++)
{
while(j>-1&&!check_m(j+1,i)) j=fail[j];
if(check_m(j+1,i)) j++;
fail[i]=j;
}
return m-fail[m-1]-1;
}
bool check_n(int x,int y)
{
for(int i=0;i<m;i++) if(a[x][i]!=a[y][i]) return false;
return true;
}
int kmp_n()
{
int j;
fail[0]=j=-1;
for(int i=1;i<n;i++)
{
while(j>-1&&!check_n(j+1,i)) j=fail[j];
if(check_n(j+1,i)) j++;
fail[i]=j;
}
return n-fail[n-1]-1;
}
int main()
{
while(scanf("%s",a[n])!=EOF) n++;
m=strlen(a[0]);
cout<<n<<" "<<m<<endl;
int mm=kmp_m(),nn=kmp_n();
for(int i=0,j;i<nn;i++)
{
for(j=0;j<mm;j++) putchar(a[i][j]);
putchar(10);
}
}