POJ 3294 Life Forms
2014-04-21 20:18
344 查看
题目大意:
在电影中你可能会感受到外来生命和地球生命这么地接近,不管是从身高、肤色、皱纹、耳朵、眉毛还是喜好等,但是有时也无法忍受没有任何人类形态的外来生物,它们长着几何形或者无任何形状的外观,比如立方体形的外星人,以及夸张一点的长成云雾状的。
在小说《星际旅行——下一代》的第146章节题为《追逐》的片段中,作者描绘到大约四分之一的声明具有相同的DNA片段。
现有多个测例,每个测例中给出n种生命的DNA序列(1 ≤ n ≤ 100,以n = 0表示输入的结束),每个序列都由小写字母组成,长度为[1, 1000],现求出它们最长的公共连续子序列,并且该最长序列至少要超过一半DNA序列所共有(比如给出6条序列,则最长序列至少要被4条所共有),如果存在多条,则按字典序从小到大输出所有符合要求的子序列,如果无一条符合条件的解则直接输出“?“,每个测例的输出之间各一个空行。
题目链接
注释代码:
无注释代码:
单词解释:
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, 子串,子链
在电影中你可能会感受到外来生命和地球生命这么地接近,不管是从身高、肤色、皱纹、耳朵、眉毛还是喜好等,但是有时也无法忍受没有任何人类形态的外来生物,它们长着几何形或者无任何形状的外观,比如立方体形的外星人,以及夸张一点的长成云雾状的。
在小说《星际旅行——下一代》的第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, 子串,子链
相关文章推荐
- 【后缀数组】【poj 3294】Life Forms
- POJ 3294 Life Forms(后缀数组求k个串的最长子串)
- POJ 3294 Life Forms (后缀数组,求出现在不少于k个字符串的最长子串)
- POJ 3294 Life Forms 二分 + 哈希
- Poj 3294 Life Forms (后缀数组 + 二分 + Hash)
- POJ3294——Life Forms 后缀数组
- poj 3294 Life Forms
- POJ 3294 Life Forms (后缀数组,求出现在不少于k个字符串的最长子串)
- poj 3294 Life Forms 求n(n>1)个字符串的最长的一个子串 后缀数组
- Poj 3294 Life Forms (后缀数组 在n个串中出现k次的最长公共子串并输出)
- POJ - 3294 Life Forms
- poj 3294 Life Forms 后缀数组
- POJ 3294 Life Forms
- poj_3294 Life Forms(后缀数组+二分)
- poj 3294 Life Forms(n个字符串中 求公共子串长度超过k得最大子串 后缀数组)
- POJ 3294 Life Forms(后缀数组+二分)
- Poj 3294 Life Forms
- POJ - 3294 Life Forms
- POJ 3294 Life Forms (后缀数组)
- POJ 3294 Life Forms(后缀数组)