听说暴力优化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; }