题目大意:
在电影中你可能会感受到外来生命和地球生命这么地接近,不管是从身高、肤色、皱纹、耳朵、眉毛还是喜好等,但是有时也无法忍受没有任何人类形态的外来生物,它们长着几何形或者无任何形状的外观,比如立方体形的外星人,以及夸张一点的长成云雾状的。
在小说《星际旅行——下一代》的第146章节题为《追逐》的片段中,作者描绘到大约四分之一的声明具有相同的DNA片段。
现有多个测例,每个测例中给出n种生命的DNA序列(1 ≤ n ≤ 100,以n = 0表示输入的结束),每个序列都由小写字母组成,长度为[1, 1000],现求出它们最长的公共连续子序列,并且该最长序列至少要超过一半DNA序列所共有(比如给出6条序列,则最长序列至少要被4条所共有),如果存在多条,则按字典序从小到大输出所有符合要求的子序列,如果无一条符合条件的解则直接输出“?“,每个测例的输出之间各一个空行。
注释代码:
/*
* Problem ID : POJ 3294 Life Forms
* Author : Lirx.t.Una
* Language : G++
* Run Time : 329 ms
* Run Memory : 2404 KB
*/
#pragma G++ optimize("O2")
#include <string.h>
#include <stdio.h>
#define TRUE 1
#define FALSE 0
//用于表示每条序列在合并序列中的
//左右端点的下标
//即left和right,分别表示左右端点
#define LFT 0
#define RHT 1
//由于最多要将100条序列合并
//由于中间间隔的特殊符号一定要互不相同
//而且最多需要99个特殊符号
//因此特殊符号的范围就从'z'+1 ~ 'z'+99了
#define CHN ( 'z' + 99 )
//DNA序列的最大数量
#define MAXK 101
//maximum feasible subsequence
//可行公共序列的最大数量
//该值用于二分所搜最长序列长度时需要用到
//当假设答案长度为mid时,有可能使可行子序列数量很大
//这个是测试过的最大值
#define MAXFSBN 673
//合并序列的最大长度
//输入数据中有超过1000长度的字符串
//因此比实际长度100100大3
//100100 = 1000 × 100 + 99 + 1
//99个特殊字符和1个'\0'
#define MAXLEN 100103
#define MAX(x,y) ( (x) > (y) ? (x) : (y) )
typedef int BOOL;
//使用的字符范围最大为'z' + 99
//超过的char的最大值127,所以使用unsigned char
typedef unsigned char uc;
int buf1[MAXLEN];
int buf2[MAXLEN];
int buf3[MAXLEN];
int sa[MAXLEN];
int h[MAXLEN];
int * rk;
uc s[MAXLEN];
//range
//rng[i][LFT]和rng[RHT]
//分别记录第i条DNA的左端点和右端点在合并字符串
//s中的下标,每条DNA序列都包含本身字符以及附加的特殊字符
int rng[MAXK][2];
//当枚举可行公共序列时,vist[i]表示该待检验的可行公共子序列
//在第i条DNA序列中是否出现过
char vist[MAXK];
//starting point of feasible subsequence
//可行公用子序列的起始字符在合并字符串中的下标位置
//fsb[i]表示第i个可行解(公共序列)的起始字符在s中的下标
int fsb[MAXFSBN];
int
keq( int *v, int x, int y, int l ) {
return v[x] == v[y] && v[x + l] == v[y + l];
}
void
bd_sa( uc *s, int n, int mv ) {
int * vi;
int * sv;
int * lv;
int * t;
int v;
int cl;
int i, j;
vi = buf1;
sv = buf2;
lv = buf3;
for ( i = 0; i <= mv; i++ ) sv[i] = 0;
for ( i = 0; i <= n; i++ ) sv[ vi[i] = s[i] ]++;
for ( i = 1; i <= mv; i++ ) sv[i] += sv[i - 1];
for ( i = n; i >= 0; i-- ) sa[ --sv[ vi[i] ] ] = i;
for ( cl = 1, v = 0; v <= n; cl <<= 1, mv = v ) {
for ( j = 0, i = n - cl + 1; i <= n; i++ ) lv[j++] = i;
for ( i = 0; i <= n; i++ ) if ( sa[i] >= cl ) lv[j++] = sa[i] - cl;
for ( i = 0; i <= mv; i++ ) sv[i] = 0;
for ( i = 0; i <= n; i++ ) sv[ vi[ lv[i] ] ]++;
for ( i = 1; i <= mv; i++ ) sv[i] += sv[i - 1];
for ( i = n; i >= 0; i-- ) sa[ --sv[ vi[ lv[i] ] ] ] = lv[i];
for ( t = vi, vi = lv, lv = t, vi[ sa[0] ] = 0, v = 1, i = 1; i <= n; i++ )
vi[ sa[i] ] = keq( lv, sa[i - 1], sa[i], cl ) ? v - 1 : v++;
}
rk = vi;
}
void
bd_h( uc *s, int n ) {
int i, j;
int cl;
for ( cl = 0, i = 0; i < n; h[ rk[i++] ] = cl )
for ( cl ? --cl : 0, j = sa[ rk[i] - 1 ]; s[i + cl] == s[j + cl]; cl++ );
}
int
iths( int idx, int k ) {//i th DNA sequence
//k为DNA的总条数
//给定一个字符在合并字符串s中的下标index(idx)
//判断其属于第几条DNA序列,返回DNA序号i
int lft, rht, mid;//二分所搜答案
lft = 1;
rht = k;
while ( lft <= rht ) {
mid = ( lft + rht ) >> 1;
if ( idx >= rng[mid][LFT] && idx <= rng[mid][RHT] )
return mid;
if ( idx < rng[mid][LFT] )
rht = mid - 1;
else
lft = mid + 1;
}
}
BOOL
chk( int cl, int n, int hlf, int k ) {//check
//检查common length下是否有可行解
//即如果解的长度为cl,则检验该可行解是否存在
//如果存在则记录可行解
//否则返回FALSE
int i, j;//计数变量
int th;
int cnt;//可行解的数量
BOOL full;
th = 0;
cnt = 0;
memset(vist, FALSE, sizeof(vist));
//枚举height值并对height进行分组
//按照sa[0] ~ sa[n]的顺序(因为题目要求按照字典序升序输出结果)
//将Height进行排列,所有在Height数组中连续的并且值大于mid的
//都具有公共的长度为cl的前缀,即按照cl值对Height进行分组
//一旦出现小于cl值的Height就代表一组分完了
//有几组大于等于cl的Height就有多少个可行的长度为cl的公共子序列
//!!注意使用full剪枝,full表示如果当前可行序列所共有的DNA已经累计到超过
//一半了,那么接下来就不用算了,就将full置TRUE,表示已经满了(即超过刚好超过一半了)
//直到当当前组结束下一组来临之前,即第一次出现h[i + 1] < cl时再将full清空
//th表示当前公共序列已经出现在多少条DNA中了
full = FALSE;
for ( i = 1; i <= n; i++ )
if ( h[i] >= cl ) {
if ( full )//重要的剪枝1
continue;
if ( !th ) {//重要的剪枝2
//th == 0表示之前th被清空过
//因此上一次的ihts-sa的计算记录被销毁,即iths-sa[i - 1]的计算机路被销毁了
//所以要重新计算一次
j = iths( sa[i - 1], k );//求排名为i - 1的后缀的起始字符位于第j条DNA中
th += !vist[j];//如果没有计算过就加1,否则表示之前已经计算过了
vist[j] = TRUE;//并将其置TRUE
}//否则就代表现在仍然处于当前公共子序列中,前一次的iths-sa值已经被计算过了
//不必重复计算
//计算当前的iths-sa
j = iths( sa[i], k );
th += !vist[j];
vist[j] = TRUE;
if ( th >= hlf ) {//如果当前公共序列已经被超过一半DNA共享
//则表示该子序列为一个可行的公共子序列
//因此可行解+1(++cnt)
//并记录
fsb[++cnt] = sa[i];//记录当前可行解起始字符在s中的下标
th = 0;//共享条数清零,为下一组做准备
memset(vist, FALSE, sizeof(vist));//vist同样需要清空
full = TRUE;//置满,以便剪枝
}
}
else {//表示一组结束,出现了分隔
full = FALSE;//清空
if ( !th )//表示碰到连续的h[i] < cl的情况
continue;
//否则就代表一组height刚刚结束
//全部清空
th = 0;
memset(vist, FALSE, sizeof(vist));
}
if ( cnt ) {//如果有可行解
//将可行解个数放在fsb[0]里面
//从1开始都是可行解起始字符的下标
*fsb = cnt;
return TRUE;
}
return FALSE;//无可行解
}
int
main() {
int iscn;
int k;//DNA总数
int hlf;//half,超过一半DNA的数量(hlf = k / 2 + 1)
int n;//合并字符串的总长(包括'\0')
int ml;//maximum length,二分起始的rht = ml
int tmp;//临时变量
int lft, rht, mid;//二分搜答案字符串的长度
int i, j;//计数变量
iscn = 0;
while ( scanf("%d", &k), k ) {
for ( ml = 0, n = 0, i = 1; i <= k; i++ ) {
scanf("%s", (char *)( s + n ));//注意类型转换
rng[i][LFT] = n;
//衔接合并,注意类型转换(如果不转换C++可能会报错)
rng[i][RHT] = ( n += strlen((char *)( s + n )) );
tmp = rng[i][RHT] - rng[i][LFT];
ml = MAX( ml, tmp );
s[n++] = 'z' + i;//隔一个特殊字符
}
s[--n] = '\0';
bd_sa( s, n, CHN );
bd_h( s, n );
//二分逼近答案
lft = 1;
rht = ml;
hlf = ( k >> 1 ) + 1;
while ( lft <= rht ) {
mid = ( lft + rht ) >> 1;
if ( chk( mid, n, hlf, k ) )//如果mid可行则纵深(扩大mid)
lft = mid + 1;
else//否则缩小
rht = mid - 1;
}//注意!!!由于是最终答案是记录是利用chk函数记录的
//因此最终的答案长度等于lft - 1
//因为只有通过chk = TRUE才能使得lft = mid + 1
//即最后一次通过的mid(即最终答案)以mid + 1 = lft的形式
//保存在lft中了,如果后面继续有二分,则mid的值会改变
//因此最终的答案就为lft - 1
if ( iscn++ )//每个测例间空一行
putchar('\n');
if ( !( lft - 1 ) )//结果长度为0
puts("?");
else//存在有长度的结果
for ( i = 1; i <= *fsb; i++ ) {
rht = fsb[i] + lft - 1;
for ( j = fsb[i]; j < rht; j++ )
putchar( s[j] );
putchar('\n');
}
}
return 0;
}
无注释代码:
#pragma G++ optimize("O2")
#include <string.h>
#include <stdio.h>
#define TRUE 1
#define FALSE 0
#define LFT 0
#define RHT 1
#define CHN ( 'z' + 99 )
#define MAXK 101
#define MAXFSBN 673
#define MAXLEN 100103
#define MAX(x,y) ( (x) > (y) ? (x) : (y) )
typedef int BOOL;
typedef unsigned char uc;
int buf1[MAXLEN];
int buf2[MAXLEN];
int buf3[MAXLEN];
int sa[MAXLEN];
int h[MAXLEN];
int * rk;
uc s[MAXLEN];
int rng[MAXK][2];
char vist[MAXK];
int fsb[MAXFSBN];
int
keq( int *v, int x, int y, int l ) {
return v[x] == v[y] && v[x + l] == v[y + l];
}
void
bd_sa( uc *s, int n, int mv ) {
int * vi;
int * sv;
int * lv;
int * t;
int v;
int cl;
int i, j;
vi = buf1;
sv = buf2;
lv = buf3;
for ( i = 0; i <= mv; i++ ) sv[i] = 0;
for ( i = 0; i <= n; i++ ) sv[ vi[i] = s[i] ]++;
for ( i = 1; i <= mv; i++ ) sv[i] += sv[i - 1];
for ( i = n; i >= 0; i-- ) sa[ --sv[ vi[i] ] ] = i;
for ( cl = 1, v = 0; v <= n; cl <<= 1, mv = v ) {
for ( j = 0, i = n - cl + 1; i <= n; i++ ) lv[j++] = i;
for ( i = 0; i <= n; i++ ) if ( sa[i] >= cl ) lv[j++] = sa[i] - cl;
for ( i = 0; i <= mv; i++ ) sv[i] = 0;
for ( i = 0; i <= n; i++ ) sv[ vi[ lv[i] ] ]++;
for ( i = 1; i <= mv; i++ ) sv[i] += sv[i - 1];
for ( i = n; i >= 0; i-- ) sa[ --sv[ vi[ lv[i] ] ] ] = lv[i];
for ( t = vi, vi = lv, lv = t, vi[ sa[0] ] = 0, v = 1, i = 1; i <= n; i++ )
vi[ sa[i] ] = keq( lv, sa[i - 1], sa[i], cl ) ? v - 1 : v++;
}
rk = vi;
}
void
bd_h( uc *s, int n ) {
int i, j;
int cl;
for ( cl = 0, i = 0; i < n; h[ rk[i++] ] = cl )
for ( cl ? --cl : 0, j = sa[ rk[i] - 1 ]; s[i + cl] == s[j + cl]; cl++ );
}
int
iths( int idx, int k ) {
int lft, rht, mid;
lft = 1;
rht = k;
while ( lft <= rht ) {
mid = ( lft + rht ) >> 1;
if ( idx >= rng[mid][LFT] && idx <= rng[mid][RHT] )
return mid;
if ( idx < rng[mid][LFT] )
rht = mid - 1;
else
lft = mid + 1;
}
}
BOOL
chk( int cl, int n, int hlf, int k ) {
int i, j;
int th;
int cnt;
BOOL full;
th = 0;
cnt = 0;
memset(vist, FALSE, sizeof(vist));
full = FALSE;
for ( i = 1; i <= n; i++ )
if ( h[i] >= cl ) {
if ( full )
continue;
if ( !th ) {
j = iths( sa[i - 1], k );
th += !vist[j];
vist[j] = TRUE;
}
j = iths( sa[i], k );
th += !vist[j];
vist[j] = TRUE;
if ( th >= hlf ) {
fsb[++cnt] = sa[i];
th = 0;
memset(vist, FALSE, sizeof(vist));
full = TRUE;
}
}
else {
full = FALSE;
if ( !th )
continue;
th = 0;
memset(vist, FALSE, sizeof(vist));
}
if ( cnt ) {
*fsb = cnt;
return TRUE;
}
return FALSE;
}
int
main() {
int iscn;
int k;
int hlf;
int n;
int ml;
int tmp;
int lft, rht, mid;
int i, j;
iscn = 0;
while ( scanf("%d", &k), k ) {
for ( ml = 0, n = 0, i = 1; i <= k; i++ ) {
scanf("%s", (char *)( s + n ));
rng[i][LFT] = n;
rng[i][RHT] = ( n += strlen((char *)( s + n )) );
tmp = rng[i][RHT] - rng[i][LFT];
ml = MAX( ml, tmp );
s[n++] = 'z' + i;
}
s[--n] = '\0';
bd_sa( s, n, CHN );
bd_h( s, n );
lft = 1;
rht = ml;
hlf = ( k >> 1 ) + 1;
while ( lft <= rht ) {
mid = ( lft + rht ) >> 1;
if ( chk( mid, n, hlf, k ) )
lft = mid + 1;
else
rht = mid - 1;
}
if ( iscn++ )
putchar('\n');
if ( !( lft - 1 ) )
puts("?");
else
for ( i = 1; i <= *fsb; i++ ) {
rht = fsb[i] + lft - 1;
for ( j = fsb[i]; j < rht; j++ )
putchar( s[j] );
putchar('\n');
}
}
return 0;
}
单词解释:
extraterrestrial:n, 天外来客; adj, 地球外的
resemble:vt, 类似,像
superficial:adj, 表面的,肤浅的
trait:n, 特点,特性
wrinkle:n, 皱纹
eyebrow:n, 眉毛
like:n, 爱好,喜好
resemblance:n, 相似,相似之处
geometric:adj, 几何的
amorphous:adj, 无定形的,非晶体的
cube:n, 立方体
slick:adj, 光滑的; vt, 使光滑
episode:n, 小说中的一段情节,插曲
trek:n/vi, 艰苦跋涉
chase:vt, 追逐
vast:adj, 广阔的
quadrant:n, 一个象限,四分之一
majority of:多数,大部分
life form:n, 生命形态
substring:n, 子串,子链