POJ 2774 Long Long Message

题目大意:

        小猫母亲最近生病,但是小猫家很穷没钱来回火车,因此只能在当地移动中心使用SMS发送消息给它母亲,但是最近移动中心的SMS系统故障,它的消息可能夹杂在其他消息或者乱码中(但是它的消息中不含有其他消息或乱码,即消息是完整的,只不过插在了其他消息或乱码当中),为确保母亲能受到有效的消息并能识别,他连续发送两次,这两次个产生一串字符序列,每条序列中都有小猫给母亲的信息(在这两个序列中的位置可能不同,两边的字符可能不同),现请找出小猫可能发送的最长信息的长度(即找出两字符串中的最长公共子序列)。

       现只有一个测例,测例中给出两字符串(长度不超过100,000),要求输出最长公共子序列长度。

题目链接

注释代码:

/*        
 * Problem ID : POJ 2774 Long Long Message
 * Author     : Lirx.t.Una        
 * Language   : C       
 * Run Time   : 594 ms        
 * Run Memory : 3524 KB        
*/

#include <string.h>
#include <stdio.h>

//number of characters
//字符的数量
//a ~ z + # + '\0'
//这也是基数排序中元素的初始权重
//由于z最大,因此就以z作为最大初始化权重
//这里是指ASCII码值
#define	CHN			'z'
//字符串最大长度 × 2
//最长公共连续子序列用后缀数组解时需要将两个两串字符合并
//并且中间用一个特殊符号(#)隔开
//因此主串的长度就是100,000 × 2 + 2(还包括#、'\0')
#define	MAXLEN		200002

#define	MAX(x,y)	( (x) > (y) ? (x) : (y) )
#define	MIN(x,y)	( (x) < (y) ? (x) : (y) )

//buffer,临时缓冲区
int		buf1[MAXLEN];
int		buf2[MAXLEN];
int		buf3[MAXLEN];

//suffix array,后缀数组
//sa[i]就表示排名为i的后缀的起始元素的下标
int		sa[MAXLEN];
//height,height数组,表示以sa[i - 1]和sa[i]为开头的
//后缀的最长公共前缀的长度
int		h[MAXLEN];
//rank,基数排序的顺序位置数组
//rank[i]就表示以下标为i的元素开头的后缀的排名
int	*	rk;

char	s[MAXLEN];//string,存放字符串

int
keq( int *v, int x, int y, int l ) {//key equal
	//倍增基数排序中比较两个位置的关键字是否相等
	//各个位置关键字有两个,一个是第一关键字还有一个是第二关键字
	//第一关键字就是v[x]和v[y],第二关键字是v[x + l]和v[y + l]
	//其中v是每个位置的值,x和y表示位置,l是combine length,即
	  //倍增基数排序的当前合并长度

	return v[x] == v[y] && v[x + l] == v[y + l];
}

void
bd_sa( char *s, int n, int mv ) {//build SA
	//使用倍增基数排序算法构造SA数组
	//string,原字符串
	//n为string中字符的个数,包括'\0',该元素rank始终为0,即排名最小的
	//maximum rank value,最大排名值

	//rank value of index i
	//s数组中i下标元素的排名值,初始化时就等于该位置字符的ASCII码大小
	//随着倍增基数排序的进行,该值(vi[i])会逐渐趋向于rank[i]
	//因此vi的长度是MAXLEN,而不是'z'
	int *	vi;
	//sum of rank value/number of rank value
	//sv[i]表示排名值为i的元素的个数
	//是基数排序的核心部分
	//基数排序的核心思想就是利用排名值的个数构造rank数组,从而获得
	//各元素的排名而间接对数组排序
	int	*	sv;
	//location of rank value
	//lv[i]表示排名值为i的元素的下标位置
	int *	lv;
	int	*	t;//temporary,临时指针变量,用于交换

	int		v;//rank value,当前排名值
	int		cl;//combine length,待排序的合并长度

	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;//间接得出排名sa

	//以上只完成了第一轮的第一关键字排名,一下就是倍增算法
	//每次先倍增地对第二关键字进行排名
	//然后再将第一和第二关键字进行合并并排名,然后就成为了下一轮的第一关键字
	//循环操作以上两步,该过程中v的值逐渐趋向于真实rank值,直到v的值完全等于rank值为止
	//最直接的表现就是v的最大值等于n
	for ( cl = 1, v = 0; v <= n; cl <<= 1, mv = v ) {
		//由于上面四行代码完成了一个字符的排名
		//因此接下来的合并排序长度就为1
		//合并排序完了以后最短的合并后的单位长度就为2了,依次类推合并长度每过一轮就倍增一次
	
		//求第二关键字的lv值
		//由于从n - cl + 1的位置开始,与其合并的元素都在外面,因此这些位置的第二关键字都为0
		//因此从左往右将其从0开始排名,即lv[0 ~ 1-cl] = n-cl+1 ~ n  
		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];
		//但是得到的仅仅是sa数组,还要合并得出vi数组才行

		//合并得到vi数组,由于要跟新vi的过程中还要利用vi,但是lv数组已经没用了
		//因此可以交换两者的空间,达到重复利用的效果
		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;//最后vi和rank就完全相等了
}

void
bd_h( char *s, int n ) {//build height
	//构造height数组

	int		i, j;
	int		cl;//common length,公共连续子序列的长度

	//由数学推导的height[ rank[i] ] ≥ height[ rank[i] - 1] - 1
	//虽然这里乍一看两个for循环应该是n^2的复杂度,其实由于上面的条件控制
	//是的复杂度降到几乎n

	//!!注意:s[n] = '\0',因此rank[n] = 0,所以rank[n] - 1 < 0,所以sa[ rk[n] - 1 ]
	//可能会导致段错误,因此i < n(第一个for语句中)

	for ( cl = 0, i = 0; i < n; h[ rk[i++] ] = cl )
		//这里cl为上一轮的height值,--就是上一轮的height值-1(见不等式)
		//回顾:height[i] = suffix(sa[i - 1])和suffix(sa[i])的最长公共前缀的长度
		for ( cl ? --cl : 0, j = sa[ rk[i] - 1 ]; s[i + cl] == s[j + cl]; cl++ );
}

int
main() {

	int		len;//第一串字符串的长度
	int		n;//合并后的总长度(包括'\0')
	int		ans;

	int		i;//计数变量

	//合并字符串
	scanf("%s", s);
	len = strlen(s);
	s[len] = '#';
	scanf("%s", s + len + 1);
	n = len + strlen( s + len );

	bd_sa( s, n, CHN );
	bd_h( s, n );

	//最长公共连续子序列长度必然为height中的最大值
	//但是注意要剪枝,并不是最大的height就一定符合要求
	for ( ans = 0, i = 1; i <= n; i++ )
		if ( h[i] > ans )
			//只有到两个排名相邻的后缀长度在第一个字符串长度两边时才表名
			//这两个后缀中分别包含了前后两个字符串中内容
			//否则这两个后缀都只包含一个字符串中的内容,因此无公共子串而言
			//注意这里的剪枝
			if ( MIN( sa[i - 1], sa[i] ) < len && MAX( sa[i - 1], sa[i] ) > len )
				ans = h[i];

	printf("%d\n", ans);

	return 0;
}

无注释代码:

#include <string.h>
#include <stdio.h>

#define	CHN			'z'
#define	MAXLEN		200002

#define	MAX(x,y)	( (x) > (y) ? (x) : (y) )
#define	MIN(x,y)	( (x) < (y) ? (x) : (y) )

int		buf1[MAXLEN];
int		buf2[MAXLEN];
int		buf3[MAXLEN];

int		sa[MAXLEN];
int		h[MAXLEN];
int	*	rk;

char	s[MAXLEN];

int
keq( int *v, int x, int y, int l ) {

	return v[x] == v[y] && v[x + l] == v[y + l];
}

void
bd_sa( char *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( char *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
main() {

	int		len;
	int		n;
	int		ans;

	int		i;

	scanf("%s", s);
	len = strlen(s);
	s[len] = '#';
	scanf("%s", s + len + 1);
	n = len + strlen( s + len );

	bd_sa( s, n, CHN );
	bd_h( s, n );

	for ( ans = 0, i = 1; i <= n; i++ )
		if ( h[i] > ans )
			if ( MIN( sa[i - 1], sa[i] ) < len && MAX( sa[i - 1], sa[i] ) > len )
				ans = h[i];

	printf("%d\n", ans);

	return 0;
}

单词解释:

frequently:adv, 频繁地,时常

mobile:adj, 机动的,不固定的

append:vt, 附加,追加

redundancy:n, 冗余

issue:n, 问题,故障

charge:vt, 收费

persuade:vt, 说服

dedicate:vt, 致力于,献给

beloved:adj, 心爱的,挚爱的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值