POJ 2774 Long Long Message (后缀数组)

题目类型 后缀数组

题目意思
问两个最长 1e5 的字符串的最长公共子串

解题方法
在第一个字符串后添加一个区别于输入的所有字符的字符(例如 '#') 后再把第二个字符串添加在后面 构成一个新的字符
求这个新字符串的 height 数组
二分最长公共子串的长度然后判断是否可行 假设当前判断的长度为 mid 
判断方法是 从小到大扫一次 height 数组 对于某几个连续的且大于 mid 的数 意味着这几个连续的数对应的公共前缀两两之间的最长公共前缀都是大于mid的 所以只要{ (所有这些后缀的左端点中最小值) <= (第一个字符串长度-mid)}且{ (所有这些后缀左端点中最大值 > (第一个字符串长度)
即表示有两个后缀的最长公共子串大于 mid 且分别属于输入的两个字符串

参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define B printf("BUG\n");
const int maxn = 1e5 + 10;
const int INF = 1<<29;
char s1[maxn*2], s2[maxn];
int len;
int height[maxn*2], sa[maxn*2], h[maxn*2], rank[maxn*2];
int c[maxn*2], x[maxn*2], y[maxn*2], sa2[maxn*2];

void get_height() {
	h[0] = 0;
	if(rank[0] != 0) {
		int i = 0, j = sa[rank[0]-1];
		while(j<len && s1[i] == s1[j]) { h[0]++; i++; j++; }
	}
	for( int i=1; i<len; i++ ) {
		h[i] = (h[i-1] > 1 ? h[i-1] : 1) - 1;
		if(rank[i] == 0) {
			h[i] = 0;
			continue;
		}
		int j = i+h[i], k = sa[rank[i]-1] + h[i];
		while(j < len && k <len && s1[j] == s1[k]) { h[i]++; j++; k++; }
	}
	for( int i=0; i<len; i++ ) {
		height[rank[i]] = h[i];
	}
}

void get_sa() {
	for( int i=0; i<len; i++ ) {
		if(s1[i] == '#') x[i] = 1;
		else x[i] = s1[i] - 'a'+2;
	}

	int dn = 1; // 倍增的当前长度 -> 从某一位开始 dn 长的串

	// x[i] : 第 i 个前缀的权值
	// sa[i] : 排第 i 的是哪个前缀
	while(1) {
		memset(c, 0, sizeof(c));
		int nMAX = 0;
		for( int i=len-dn; i<len; i++ ) y[i] = 0;
		for( int i=0; i<len-dn; i++ ) { y[i] = x[i+dn]; nMAX = max(nMAX, y[i]); }

		memset(c, 0, sizeof(c));
		for( int i=0; i<len; i++ ) c[y[i]]++;
		for( int i=1; i<=nMAX; i++ ) c[i] += c[i-1];
		for( int i=len-1; i>=0; i-- ) {
			sa[--c[y[i]]] = i;
		}

		memset(c, 0, sizeof(c));
		for( int i=0; i<len; i++ ) c[x[sa[i]]]++;
		for( int i=0; i<len; i++ ) nMAX = max(nMAX, x[i]);
		for( int i=1; i<=nMAX; i++ ) c[i] += c[i-1];
		for( int i=len-1; i>=0; i-- ) {
			sa2[--c[x[sa[i]]]] = sa[i];
		}
		for( int i=0; i<len; i++ ) sa[i] = sa2[i];
		c[sa[0]] = 1;
		int rk = 1;
		for( int i=1; i<len; i++ ) {
			if(x[sa[i]] == x[sa[i-1]] && y[sa[i]] == y[sa[i-1]]) {
				c[sa[i]] = rk;
			}
			else c[sa[i]] = ++rk;
		}
		if(rk == len) break;
		for( int i=0; i<len; i++ ) x[i] = c[i];
		dn *= 2;
	}
	for( int i=0; i<len; i++ ) rank[sa[i]] = i;
}

bool judge(int l, int len1, int len2) {
	int nmin = INF, nmax = -INF;
	for( int i=1; i<len; i++ ) {
		if(height[i] >= l) {
			nmin = min(nmin, min(sa[i], sa[i-1]));
			nmax = max(nmax, max(sa[i], sa[i-1]));
			if(nmin <= len1-l && nmax > len1) return true;
		}
		else { nmin = INF; nmax = -INF; }
	}
	return false;
}

int main() {
	//freopen("in", "r", stdin);
	while(scanf("%s%s", s1, s2) != EOF) {
		int len1 = strlen(s1), len2 = strlen(s2);
		len = len1;
		s1[len++] = '#';
		for( int i=0; i<len2; i++ ) s1[len++] = s2[i];
		s1[len] = '\0';
		get_sa();
		get_height();
		int L = 1, R = min(len1, len2);
		int ans = 0;
		int cnt = 0;
		while(L <= R) {
			int mid = (L+R)/2;
			if(judge(mid, len1, len2)) {
				ans = mid;
				L = mid + 1;
			}
			else R = mid - 1;
		}
		printf("%d\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值