您的位置:首页 > Web前端

POJ 3294 Life Forms

2014-04-21 20:18 344 查看
题目大意:

在电影中你可能会感受到外来生命和地球生命这么地接近,不管是从身高、肤色、皱纹、耳朵、眉毛还是喜好等,但是有时也无法忍受没有任何人类形态的外来生物,它们长着几何形或者无任何形状的外观,比如立方体形的外星人,以及夸张一点的长成云雾状的。

在小说《星际旅行——下一代》的第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
的顺序(因为题目要求按照字典序升序输出结果)
//将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, 子串,子链
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: