POJ 2185 Milking Grid(KMP)

题目传送门


解题思路:

求左上角开始的最小矩阵可以延拓成整个图形,由于边界可以不完整延拓,所以想到kmp求循环节的思想。

我们先求一个ansc,使得每一行都可以通过前ansc个延拓完。

如果我们求出了一个ansc,那么我们我们就可以将每行前ansc个字符看做“一个字符”,然后进行列的kmp得出列的最小循环节ansr,最小矩阵就是ansc*ansr

显然当ansc最小时,ansr便能求得最小

那么如何求ansc?

是每行最小循环节的LCM(最小公倍数)吗?

不是。

看一下这组数据:

2 8

ABCDEFAB

AAABCAAA

这两行最小循环节为6,5,求得LCM>8,所以照我们所想ansc = 8,然而很明显看出ansc = 6

问题是什么呢?每一行不止一种循环节长度,而最小循环节的LCM不一定最小,我们实际需要做的是这样:

保存下每一行的所有可行循环节长度,每一行枚举一个长度,求所有行的LCM,但是这样做肯定不行,我们需要优化。

首先我们对于每行用于枚举的长度可以将某个倍数的长度看做一类,我们只需要枚举这里面最小的一个就可以,

比如5,10,15,20,我们只需枚举5,因为枚举其他几个求得的LCM只大不小。

好了,那我们现在的问题是:

1.如何求出每一行所有有效的数进行枚举(有效的数,这对解决第二个问题很关键)

2.以及如何求每行枚举不同数时的LCM

第二个问题的解决方法是:

开一个cnt[80]数组,每次枚举一个数,我们就把这个数以及这个数的倍数在cnt数组上标记,每一行的数枚举完毕后,我们

最后取cnt[i] = 行数的最小值即为ansc

比如我枚举这一行的循环节大小为3,那么我就标记cnt[3],cnt[6]...

这样最终标记次数 = 行数的最小的就是ansc了

第一个问题:

首先我们知道求出每行所有循环节的求法:

就是fail不断回溯,最长相同前后缀越来越短,求得的循环节越来越长

int now = fail[len];
int tot = 0;
while (now) cir[tot++] = len-now,now = fail[now];

当前循环节循环次数超过两次时,每次跳fail,新循环节长度 = 上一个长度 + 当前最小循环节(就是当前最小循环节的x倍)

过程中求得的都是无效的,当最后循环次数小于等于2时,我们对于此时循环缀余部分(或者循环次数刚好等于2时是一个完整循环节)单独抠出来重新求最小循环节,此时我们可以得到下一个有效循环节长度。有效是因为下一个循环节必定是某个倍数的最小循环节+一个比当前最小循环节小的数

举个例子:

因此我们怎么跳过中间两种无用的,直接取求第四种循环节长度呢?

就是直接用总长和当前有效循环节长度,下一个直接去找缀余就行了。


代码:

#include<cstdio>
#include<cstring>

using namespace  std;

#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define debug(x) printf("---- %s ----\n",#x)
#define pt(x) printf("%s = %d\n",#x,x)

const int N = 1e4+5;

char mp[N][80];
int fail[80];
int strfail[N];
int cnt[80];

int kmp(char *s,int len){
    fail[0] = -1;
    for (int i=0,j=-1;i<len;){
        if (j==-1||s[i]==s[j]) i++,j++,fail[i] = j;
        else j = fail[j];
    }
    return fail[len];
}

void mark(char *s,int len){
    int cir = len - kmp(s,len),i;//i走出for循环-循环节长度,就等于缀余的起点
    for (i = cir;i<len;i+=cir) cnt[i]++;
    cnt[len]++; 
    i -= cir;  
    int f;//用于记录fail[len]
    while (f = kmp(s+i,len-i)){
        cir = len - f;
        for (i = cir;i<len;i+=cir) cnt[i]++;
        i -= cir;
    }
}

int strkmp(int len,int l){
    strfail[0] = -1;
    for (int i=0,j=-1;i<len;){
        if (j==-1||strncmp(mp[i],mp[j],l)==0) i++,j++,strfail[i] = j;
        else j = strfail[j];
    }
    //for1(i,0,len) printf("strfail[%d]=%d\n",i,strfail[i]);
    return len-strfail[len];
}

int main()
{
    int r,c;
    scanf("%d %d",&r,&c);
    for0(i,0,r) scanf("%s",mp[i]);
    for0(i,0,r) mark(mp[i],c);
    int ansc,ansr;
    for1(i,1,c) if (cnt[i]==r){ansc = i;break;}
    //pt(ansc);
    ansr = strkmp(r,ansc);
    //pt(ansr);
    printf("%d\n",ansc*ansr);
    return 0;
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值