POJ 2406 Power Strings KMP 或 后缀数组

35 篇文章 1 订阅
8 篇文章 0 订阅

题目大意:

就是给出一个串S 长度不超过10^6, 求最大周期使得S = a^n也就是S是有n个字符串a连接起来的,求最大的n(也就是找到最短的a即可)


大致思路:

首先利用KMP的next数组可以知道循环节的个数, 为n/(n - next[n]) n是S的长度, 这个感觉还是有点晕...

另外一个做法是使用后缀数组


KMP的做法:

代码如下:

Result  :  Accepted     Memory  :  5024 KB     Time  :  141 ms

/*
 * Author: Gatevin
 * Created Time:  2015/2/2 15:07:55
 * File Name: Iris_Freyja.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

/*
 * 首先如果S = a^n由next数组定义next[n]的值满足S[0~next[n] - 1] == S[(n - next[n]) ~ (n - 1)]
 * 如果设S1 = a^(n - 1)则S = S1*a那么在n处失配时由于由next数组的定义转到S1的结尾位置后一位进行匹配
 * S[0~next[n] - 1] == S[(n - next[n]) ~ (n - 1)] = S1
 * 那么n - next[n]刚好是循环节的长度, 且n % (n - next[n]) == 0
 * 有种证明说不清楚的感觉....但是当n % (n - next[n]) == 0 的时候的确很好证明循环节是n - next[n]
 * 可是n % (n - next[n]) != 0的时候为什么没有循环节这个还没证明出来...
 */
char s[1000010];
int next[1000010];

int main()
{
    while(scanf("%s", s))
    {
        if(strlen(s) == 1 && s[0] == '.') break;
        memset(next, 0, sizeof(next));
        int n = strlen(s);
        for(int i = 1; i < n; i++)
        {
            int j = i;
            while(j > 0)
            {
                j = next[j];
                if(s[i] == s[j])
                {
                    next[i + 1] = j + 1;
                    break;
                }
            }
        }
        if(n % (n - next[n]) == 0)
            printf("%d\n", n/(n - next[n]));
        else
            printf("1\n");
    }
    return 0;
}



另外是后缀数组的做法:

复杂虽然是O(n)但是常数看来很大= =

思路见代码注释, 另外求后缀数组的时候用倍增算法超时了..要用DC3算法

Result  :  Accepted     Memory  :  45508 KB     Time  :  2750 ms

/*
 * Author: Gatevin
 * Created Time:  2015/2/2 19:21:53
 * File Name: Iris_Freyja.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

#define maxn 1000233
#define F(x) ((x)/3 + ((x) % 3 == 1 ? 0 : tb))
#define G(x) ((x) < tb ? (x)*3 + 1 : ((x) - tb)*3 + 2)

/*
 * DC3算法求后缀数组...倍增算法会超时..
 */
int wa[maxn], wb[maxn], wv[maxn], Ws[maxn];
int c0(int *r, int a, int b)
{
    return r[a] == r[b] && r[a + 1] == r[b + 1] && r[a + 2] == r[b + 2];
}

int c12(int k, int *r, int a, int b)
{
    if(k == 2) return r[a] < r[b] || (r[a] == r[b] && c12(1, r, a + 1, b + 1));
    else return r[a] < r[b] || (r[a] == r[b]  && wv[a + 1] < wv[b + 1]);
}

void sort(int* r, int *a, int *b, int n, int m)
{
    int i;
    for(i = 0; i < n; i++) wv[i] = r[a[i]];
    for(i = 0; i < m; i++) Ws[i] = 0;
    for(i = 0; i < n; i++) Ws[wv[i]]++;
    for(i = 1; i < m; i++) Ws[i] += Ws[i - 1];
    for(i = n - 1; i >= 0; i--) b[--Ws[wv[i]]] = a[i];
    return;
}

void dc3(int *r, int *sa, int n, int m)
{
    int i, j, *rn = r + n, *san = sa + n, ta = 0, tb = (n + 1) / 3, tbc = 0, p;
    r[n] = r[n + 1] = 0;
    for(i = 0; i < n; i++) if(i % 3 != 0) wa[tbc++] = i;
    sort(r + 2, wa, wb, tbc, m);
    sort(r + 1, wb, wa, tbc, m);
    sort(r, wa, wb, tbc, m);
    for(p = 1, rn[F(wb[0])] = 0, i = 1; i < tbc; i++)
        rn[F(wb[i])] = c0(r, wb[i - 1], wb[i]) ? p - 1 : p++;
    if(p < tbc) dc3(rn, san, tbc, p);
    else for(i = 0; i < tbc; i++) san[rn[i]] = i;
    for(i = 0; i < tbc; i++) if(san[i] < tb) wb[ta++] = san[i] * 3;
    if(n % 3 == 1) wb[ta++] = n - 1;
    sort(r, wb, wa, ta, m);
    for(i = 0; i < tbc; i++) wv[wb[i] = G(san[i])] = i;
    for(i = 0, j = 0, p = 0; i < ta && j < tbc; p++)
        sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
    for(; i < ta; p++) sa[p] = wa[i++];
    for(; j < tbc; p++) sa[p] = wb[j++];
    return;
}

int rank[maxn], height[maxn];
void calheight(int* r, int* sa, int n)
{
    int i, j, k = 0;
    for(i = 1; i <= n; i++) rank[sa[i]] = i;
    for(i = 0; i < n; height[rank[i++]] = k)
        for(k ? k-- : 0, j = sa[rank[i] - 1]; r[i + k] == r[j + k]; k++);
    return;
}

int s[3*maxn];
char ts[maxn];
int sa[3*maxn];
int lcp[maxn];

/*
 * 如果我们暴力枚举S的前k个字符会是一个循环节,
 * 那么Suffix(0)(从s[0]开始的后缀)与Suffix(k)会具有LCP值是n - k的关系
 * 如果n % k == 0且LCP(rank[0], rank[k]) == n - k, 则前k个字符是S的循环节(可证明)
 * 而由LCP Theorem可知对于i < j 有 LCP(i, j) = min{LCP(k - 1, k) | i + 1 <= k < j}
 * 因此如果用lcp[i]表示LCP(rank[0], i)的话
 * 利用height数组有height[i] = LCP(i, i - 1)
 * 则对于i < rank[0] lcp[i] = min(height[i + 1], lcp[i + 1])
 * 对于i > rank[0] lcp[i] = min(height[i], lcp[i - 1])
 * 递推求出lcp数组即可(这里因为rank[0]固定所以没有必要使用RMQ的查询问题, 预处理更简单)
 * 这样枚举长度k从1到n/2, 如果存在lcp[ran[k]] = n - k 即LCP(rank[0], rank[k])的话, k是满足条件的循环节
 * 找出最小的这样的k即可
 */
int main()
{
    while(scanf("%s", ts))
    {
        int n = strlen(ts);
        if(n == 1 && ts[0] == '.') break;
        for(int i = 0; i < n; i++) s[i] = ts[i];
        s[n] = 0;
        dc3(s, sa, n + 1, 260);//可打印字符单位不会超过ASCII码表= =
        calheight(s, sa, n);
        //递推求解lcp[i] = LCP(rank[0], i)
        int t = rank[0];
        lcp[t - 1] = height[t];
        lcp[t + 1] = height[t + 1];
        for(int i = t - 2; i >= 0; i--) lcp[i] = min(height[i + 1], lcp[i + 1]);
        for(int i = t + 2; i <= n; i++) lcp[i] = min(height[i], lcp[i - 1]);
        
        for(int k = 1; k <= (n >> 1); k++)
        {
            if(n % k == 0 && lcp[rank[k]] == n - k)//寻找满足条件的最小的k
            {
                printf("%d\n", n/k);
                goto nex;
            }
        }
        printf("1\n");
        nex:;
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值