POJ 3294 Life Forms

题目大意:

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

        在小说《星际旅行——下一代》的第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, 子串,子链

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值