听说暴力优化0ms通关。 但是这里要说的显然不是暴力的方法~
还是喜欢中等大小的字体啦啦啦~
我知道论文上有讲,但是如果论文上的看懂了,你一定不会在百度搜这个了……我反正当时没看的很明白,大神的解释语言十分精简,精简到了,对于弱B的我,我就看不懂了……
题目大意:
一个字符串T,他可能里面有一段子串S, S由一个S的子串P重复K次而成。
比如 qwerty abcabcabc 这个串,他后面那部分abcabcabc是由abc重复3次而成。
这个题要你求出最大的K所代表的子串,如果有多个,输出字典序最小的那个。 对于上面的那个数据,就要输出abcabcabc 。
希望题目意思我的解释还算清楚……………………
======================华丽的分割线============
前提条件: 后缀数组掌握, RMQ解法至少掌握一种。 RMQ不懂的可以看我前一篇文章,我感觉讲的还算清楚,看不懂的话留言,如果感觉我有不对的地方也可以提出~大家共同进步……我会尽量用大家能看懂的方式来讲解。(但是我的语文是体育老师教的,所以看不懂我的文章不是你弱……而是我的语文太弱了)
好了进入正题:
这个题的大体思想的第一步, “子串是由长度为L的串重复R次而成”。 对于这个问题,我们既不知道L,也不知道R……
所以第一步,我们要穷举L。 (当然, R= 1的情况,只要找出整个串ASCII码最小的一个字母输出即可……重复1次嘛!那一个字母重复一次,就是字母本身啦! 当然1个最小的字母,一定也是字典序最小的啦! 以后对于R=1的情况我就不说啦,只考虑R>=2的情况。)
对于一个长度为L的串,他起点位置如果是i, 终点位置一定是i + L - 1。
因为至少重复2次,那么这个串最短也要是2L, 那么如果起点是i,终点位置一定是i + 2L -1
因为S串是长度为L的串重复而成,那么S的长度首先必须是L的倍数…… 【1】
下面要引入一个小结论…… 其实大神都知道的……我还看了一会儿才明白……这也是这道题的核心部分。
对于数组下标: 0 1 2 3 4 5 6 7 8 9 ,如果L=3,重复至少2次,那么这个串,一定会经过0,3,6,9.... 这些3的整数倍的下标,其中连续的2个。
比如长度为6的串, 可以是[0 1 2 3 4 5](经过0,3) 也可以是[1,2,3,4,5,6](经过3,6) 也可以是[2,3,4,5,6,7](经过3,6) 但是不管怎么样,他一定经过0,3,6,9这些3的整数倍中,连续的两个。【2】
下面对i 和 i + L求最长公共前缀。 这个前缀有啥特殊的地方呢!
我们来看一个例子i = 6 L = 3的情况 也就是s[6]和s[9]的最长公共前缀。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
a b c a b c a ……
a b c a ……
如果这个串a[6] = a[9], a[7]=a[10] a[8]=a[11]的话,那么整个串,的[6 7 8] = [9 10 11]。 如果这个串还能继续匹配下去,出现a[9] = a[12] a[10] =a[13]的话, 其实就重复了!
因为a[6] = a[9], a[9] = a[12] 实际上就是a[6] = a[12]
同理,如果a[10] = a[13],那么就是a[7]=a[13]。 也就是说,后面的串都是和a[6] a[7] a[8]是相同的! 【希望这一大段话,如果看不懂的话,最好自己动笔划一划,举个例子来理解是很好的办法……】
那么现在好了,当前从i(上面的例子是6)位置开始, L(上面的例子是3)次一循环的串,最长延续到的位置我们已经知道了。
最长公共后缀是k的话,那么显然从i位置循环了k / L + 1次。 (最长公共前缀是6的话,那么显然i = 6开始,一直到11, 从9开始一直到14都是一样的串, K的长度只是从9开始的长度,K/L也就是从9开始往后的串的长度的循环次数, +1是因为[6 7 8]这个串没算。)
现在问题来了! 不见得所有串都是从L的整数倍位置开始的!!! 因为既然有后缀,就一定也有前缀!前缀的道理同理~
但是呢,我们因为是把整个串的L的位置都穷举了一遍(0,3,6,9…… 当L=3的时候,每一个3的整数倍我们都穷举了一次)
实际上,我们只要考虑一点就行了, 比如i =6, L=3的时候,我们只要考虑是不是从4开始,或者5开始。 不用考虑从3开始的情况,因为从3开始的情况我们已经算过了。【3】 (这个地方我自己当时理解了好一会儿)
举个例子:
3 4 5 6 7 8 9 10 11 12 13 14
a b c a b c a b c a b c
这个串,i =9,L=3。
9开始往后的串是abc abc
8开始往后的串是cab cab
7开始往后的串是bca bca
而6开始的呢? 6作为3的整数倍的点,我们已经算过啦!不需要考虑了。
现在的核心问题是,找前缀的问题。 我们总不能因为i=6,L=3, 还把4,5,6(6,和3之间的部分)分别和4+ 3, 5+3,6+3的位置求一次公共前缀吧?
其实不用的,因为整个串一定是L的整数倍。如果从s[6]和s[9]的最长公共前缀是7的话,那么我们必须从4开始,最长公共前缀是9,才能算得到一个有用的串。 如果从5开始,那么最长公共前缀最多也就是8.(既然不是L的整数倍,有啥用呢?重复次数又不增加)
所以我们只需要根据i和i+L这2个串的最长公共前缀的长度,来算出,【如果我要让串的重复次数+1, 我必须从i - p 的位置开始,并且s[i - p .... i] 部分是相同的[i + L - p ....i + L]】。
根据上述描述,我们就可以求出2个东西
最多重复R次, L1,L2,L3…… 长度的串都可以重复R次。然后我们穷举SA[1],SA[2]……
看看SA[i] 开头长度为L1个串,是否能重复R次。 这里的判定就比较简单了, 直接看 sa[i] 和sa[i]+L的最长公共前缀K, K/L+1是否等于R即可。 最先算出的SA[i],就一定是字典序最小的……
好了……希望大家都能看懂…… 到这里我的后缀数组练习也算暂时结束了……
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int max_n = 100000 + 20;
char input[max_n];
int a[max_n], wa[max_n], wb[max_n], tub[max_n], wv[max_n];
int height[max_n], rank[max_n], sa[max_n];
inline bool cmp(int *r, int a, int b, int l)
{return r[a] == r[b] && r[a + l] == r[b + l];}
inline void da(int *r, int *sa, int n, int m)
{
int i, j, p, *x = wa, *y = wb;
for (i = 0; i != m; ++ i) tub[i] = 0;
for (i = 0; i != n; ++ i) tub[x[i] = r[i]] ++;
for (i = 1; i != m; ++ i) tub[i] += tub[i - 1];
for (i = n - 1; i >= 0; -- i) sa[--tub[x[i]]] = i;
for (j = 1; p != n; m = p, j *= 2)
{
for (p = 0, i = n - j; i != n; ++ i) y[p ++] = i;
for (i = 0; i != n; ++ i) if (sa[i] >= j) y[p ++] = sa[i] - j;
for (i = 0; i != n; ++ i) wv[i] = x[y[i]];
for (i = 0; i != m; ++ i) tub[i] = 0;
for (i = 0; i != n; ++ i) tub[wv[i]] ++;
for (i = 1; i != m; ++ i) tub[i] += tub[i - 1];
for (i = n - 1; i >= 0; -- i) sa[-- tub[wv[i]]] = y[i];
for (swap(x, y), i = 1, p = 1, x[sa[0]] = 0; i != n; ++ i)
x[sa[i]] = cmp(y, sa[i], sa[i - 1], j) ? p - 1: p ++;
}
}
inline void calheight(int *r, int *sa, int n)
{
int k = 0, j;
for (int i = 1; i <= n; ++ i) rank[sa[i]] = i;
for (int i = 0; i != n; height[rank[i ++]] = k)
for (k?k--:0, j = sa[rank[i] - 1]; r[i + k] == r[j + k]; ++ k );
}
int st[max_n][25];
inline void makermq(int *r, int n)
{
for (int i = 0; i != n; ++ i) st[i][0] = r[i] ;
for (int j = 1; (1 << j) <= n; ++ j)
for (int i = 0; i + (1 << j) - 1 < n; ++ i)
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
inline int ask(int l, int r)
{
int tmp = (int)(log(r - l + 1)/log(2));
return min(st[l][tmp], st[r - (1 << tmp) + 1][tmp]);
}
inline int lcp(int a, int b)
{
int A = rank[a], B = rank[b];
if (A > B) swap(A, B);
++A;
return ask(A, B);
}
int ans[max_n];
inline void doit(int n) //串长度
{
int mx = 0, cut = 0;
for (int l = 1; l != n; ++ l)
{
for (int j = 0; j + l < n ; j += l)
{
int k = lcp(j, j + l);
int r = k / l + 1; //重复次数
int pre = j - (l - k % l);
if (pre >= 0)
{
k = lcp(pre, pre + l);
if (k / l + 1 > r) r = k / l + 1;
}
if (r == mx) ans[cut ++] = l;
if (r > mx)
{
mx = r;
cut = 0;
ans[cut ++] = l;
}
}
}
bool flag = false;
int pos, rlen;
for (int i = 1; i <= n && !flag; ++ i)
for (int j = 0; j != cut; ++ j)
{
if (sa[i] + ans[j] > n) continue;
int tmp = lcp(sa[i], sa[i] + ans[j]);
if (tmp / ans[j] + 1 == mx)
{
pos = sa[i];
rlen = ans[j];
flag = true;
break;
}
}
for (int i = pos; i != pos + mx * rlen; ++ i) printf("%c", input[i]);
printf("\n");
}
int main()
{
int tt = 0;
while (1)
{
gets(input);
if (input[0] == '#') break;
printf("Case %d: ", ++tt);
int len = strlen(input);
for (int i = 0; i != len; ++ i) a[i] = input[i];
a[len] = 0;
da(a, sa, len + 1, 200); //后缀数组求解
calheight(a, sa, len); //后缀数组求height
makermq(height, len + 1); //构造ST算法的RMQ预处理
doit(len); //最终的求解判定
}
return 0;
}