题目
http://poj.org/problem?id=3080
题意
有m个(2<=m<=10)不包含空格的字符串,长度为60个字符,求所有字符串中都出现过的最长公共子序列,若该子序列长度小于3,输出"no significant commonalities",否则,输出字典序最小且长度最大的公共子序列。
思路
由于数据较小,其实可以使用比较暴力的思路,但这里为了复习后缀数组采用后缀数组的方法。
1. 将多个字符串合为一个目标字符串buff,为了防止程序无法分清字符串连接处,每个连接处添加一个空格,形如AAAA GGGG。
2. 建立后缀数组,这里采用基数排序的方法。
a. 令rk数组记录后缀的字典序排名,也即rk[i]为从第i个字符开始的后缀的字典序排名,排名从0开始。
b. 令sa数组记录排序后的后缀,也即sa[i]为字典序第i的后缀的第一个字符的位置,例如buff='abba',则sa[0]=4代表空串,sa[1]=3代表a,sa[2]=0代表abba,依次类推。
c. 进行基数排序的初始排序,排序第一个字符。
d. 初始化跨度k为1,每次完成下面的循环,k乘以2倍,也即每次通过目前的rk数组把已经排序的部分长度扩展2倍。
e. 用基数排序对后缀[k,2k]进行排序。
解释:此时后缀[0,k]有序,也即前k个字符一定是按照字母序排序的,举例来说,排序可能是这样,k=2:空串,a,aadd,aacc,abaa,abcc。
此时后面k个字符,也即[k,2k]部分是无序的,但是[k,2k]部分的顺序可以和[0,k]部分一一对应起来,因为[k,2k]部分如果不是空串,那么一定是另一个后缀的[0,k]部分。
这一步根据这种关系将后缀数组sa排列为[k,2k]为主键,[0,k]为第二位键字典序递增顺序。
f. 用基数排序对后缀[0,k]进行排序。
解释:由于上一步排列的顺序,根据基数排序的特点,后缀数组将会变为[0.k]为主键,[k,2k]为第二位键字典序递增顺序,也即[0,2k]遵从字典序排列。
g. 根据sa计算rk,此处若两个后缀[0,2k]部分相同,则对应rk也应当相同。
h. 循环直至k >= len,此时明显全部遵从字典序排列。
i. 根据sa计算rk,此处rk[sa[i]] = i,此时rk的含义将发生改变,变为sa的索引,注意此处若两个后缀[0,2k]相同,其对应rk不相同。
3. 建立高度数组h。(对于从i开始的后缀,如果其高度(即该后缀和在字典序上最接近它且比它字典序小的那个后缀的公共前缀长度)为height,那么i+1开始的后缀高度至少为height-1,利用这一性质,以字符串原有的顺序遍历后缀并且计算其高度。)
4. 得出答案,由题意,设答案的长度为ans,第一个字符是目标串的第start个字符,则h数组中一定有一段连续的子序列都大于等于ans,且对应的后缀分别来自目标字符串的m段。
a. 建立优先队列,用于存储后缀的高度和index,并获知在当前扫描的这一段中出现过的最小高度。
b. 用cnt数组监视当前扫描的这一段中每段有多少个后缀出现。可以通过cnt数组获知被扫描的这一段中目标段中有多少段的后缀出现过,设这个值为num。
c. 设起点为s,若目标串长度为len,则s从0循环至len,对每次循环:
i. 初始化e=s+1,e不断向后扫,h[s,e]即为当前被扫描的段,直到num==m。
ii.将最小高度与目前存储的ans进行比较,若最小高度较大,则ans赋值为最小高度,start赋值为s。
解释:由于rk和h是对应的,所以start被赋予的会是字典序最小长度最长的公共子序列的rk,可以通过sa输出答案。
感想
这道题卡在三个点上:1. 基数排序,先对后半段排序,再对前半段排序。
2. 基数排序,因为在排序过程中将len加入排序,导致边界出错。
3. 基数排序,因为rk是对应于目标串的位置的,所以先对后半段排序时不用改变rk。
代码
#include <cstdio>
#include <map>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include <assert.h>
#include <sstream>
#include <cctype>
#include <queue>
#include <stack>
#include <map>
#include <iterator>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef unsigned long long ull;
typedef long long ll;
typedef long double ld; const int basenum = ;
const int basenump1 = basenum + ;
const int maxm = ;
const int maxn = basenump1 * maxm + basenump1; char buff[maxn]; int m;
int len; int rk[maxn];
int trk[maxn];
int sa[maxn];
int tsa[maxn];
int cnt[maxn];
int h[maxn]; void debugrk(int *sa, int l){
for(int i = ;i <= len;i++){
printf("rk[%d]:%d ",i,rk[i]);
for(int j = i;j < len && j < i + l;j++){
putchar(buff[j]);
}
puts("");
}
puts("");
} void debugsa(int *sa, int l){
for(int i = ;i <= len;i++){
printf("sa[%d]:%d rk %d ",i,sa[i], rk[sa[i]]);
for(int j = sa[i];j < len && j < sa[i] + l;j++){
putchar(buff[j]);
}
puts("");
}
puts("");
} void debugh(){
for(int i = ;i <= len;i++){
printf("POS: %d h[%d]:%d ", sa[i],i,h[i]);
for(int j = sa[i];j < len;j++){
putchar(buff[j]);
}
puts("");
}
puts("");
} void input()
{
scanf("%d",&m);
len = m * basenump1 - ;
for(int i = ; i < m; i++)
{
scanf("%s", buff + i * basenump1);
if(i + < m)buff[i * basenump1 + basenum] = ' ';
}
} void build()
{
memset(rk, , sizeof rk);
memset(cnt, , sizeof cnt);
rk[len] = trk[len] = ;
sa[] = tsa[] = len;
for(int i = ; i < len; i++)
{
rk[i] = buff[i];
cnt[rk[i]]++;
}
// first radix sort
for(int i = ; i < ; i++)
{
cnt[i] += cnt[i - ];
}
for(int i = len - ; i >= ; i--)
{
sa[cnt[rk[i]]--] = i;
}
for(int k = ; k < len; k <<= )
{
memset(cnt, , sizeof cnt);
for(int i = ; i < len; i++)
{
cnt[(i + k < len)?rk[i + k]:]++;
}
/*
Or you can use
for(int i = len;i > 0;i--){
cnt[(sa[i] + k < len)?rk[sa[i] + k]:0]++;
}
*/
for(int i = ; i <= len; i++)
{
cnt[i] += cnt[i - ];
}
for(int i = len; i > ; i--)
{
int ind = cnt[(sa[i] + k < len)?rk[sa[i] + k]:]--;
tsa[ind] = sa[i];
}
memset(cnt, , sizeof cnt);
for(int i = ; i < len; i++)
{
cnt[rk[i]]++;
}
/*
Or you can use
for(int i = len;i > 0;i--){
cnt[rk[sa[i]]]++;
}
*/
for(int i = ; i <= len; i++)
{
cnt[i] += cnt[i - ];
}
for(int i = len; i > ; i--)
{
int ind = cnt[rk[tsa[i]]]--;
sa[ind] = tsa[i];
}
for(int i = , now = sa[i], former = sa[i - ]; i <= len; i++, now = sa[i], former = sa[i - ])
{
trk[now] = trk[former];
int nownxt = min(now + k, len);
int formernxt = min(former + k, len);
if(rk[now] != rk[former] || rk[nownxt] != rk[formernxt]){ trk[now]++;
}
}
for(int i = ; i < len; i++)
{
rk[i] = trk[i];
}
}
for(int i = ;i <= len;i++){
rk[sa[i]] = i;
}
memset(h, , sizeof h);
//debugsa(sa, len);
//debugrk(sa, len);
for(int i = , height = ; i < len; i++)
{
if(height)height--;
int former = sa[rk[i] - ];
while(i + height < len && former + height < len && buff[i + height] == buff[former + height])height++;
h[rk[i]] = height;
}
//debugh();
} void solve()
{
build();
memset(cnt, , sizeof cnt);
priority_queue<P, vector<P>, greater<P> >que;
int num = ;
int ans = ;
int start = ;
for(int s = ,e = ; s <= len; s++)
{
while(num < m && e <= len)
{
que.push(P(h[e], e));
int ind = sa[e] / basenump1;
if(cnt[ind] == )num++;
cnt[ind]++;
e++;
}
while(que.top().second < s){
que.pop();
}
if(num == m && ans < que.top().first)
{
ans = que.top().first;
start = s - ;
}
if(s > ){
int ind = sa[s - ] / basenump1;
if(cnt[ind] == )num--;
cnt[ind]--;
}
}
if(ans < )puts("no significant commonalities");
else
{
for(int i = ; i < ans; i++)
{
putchar(buff[sa[start] + i]);
}
putchar('\n');
}
} int main()
{
freopen("input.txt", "r", stdin);
int t;
scanf("%d", &t);
for(int ti = ; ti < t; ti++)
{
input();
solve();
}
return ;
}