其他分享
首页 > 其他分享> > 【ybt金牌导航2-2-1】最长公共子串

【ybt金牌导航2-2-1】最长公共子串

作者:互联网

最长公共子串

题目链接:ybt金牌导航2-2-1

题目大意

要找一个字符串,使得它的长度尽可能的大,而且对于每个字符串,它在字符串或者它的翻转串的其中一个串中出现过。
只要输出最长的长度即可。

思路

这道题我们考虑用 SA 加二分来做,虽然有很多解法。

首先,你把每个字符串和他们的翻转串拼接到一个字符串中,然后中间用互不相同的字符隔开。
然后跑 SA,得出 s a i sa_i sai​。

然后想到最长公共子串,我们会想到求 height 数组。
那你如果要二分答案,你就要找到一个区间,使得它包含了每个字符串的,然后它 height 值里面的最小值还要大于你二分的答案。

那这个你扫一遍过去找到每个区间来看行不行就可以了。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

int n, m, T, t;
int p, tmp, sn, c[100001], height[100001], str[100001];
int sa[100001], rak[100001], tp[100001], tax[100001];
char s[100001];
queue <int> q;
bool in[100001];

void csh() {
	n = 0;
	tmp = 0;
	memset(sa, 0, sizeof(sa));
	memset(rak, 0, sizeof(rak));
	memset(tp, 0, sizeof(tp));
	memset(tax, 0, sizeof(tax));
	memset(c, 0, sizeof(c));
	memset(s, 0, sizeof(s));
	memset(height, 0, sizeof(height));
	memset(str, 0, sizeof(str));
	memset(in, 0, sizeof(in));
}

void paixu() {
	for (int i = 1; i <= m; i++)
		tax[i] = 0;
	for (int i = 1; i <= n; i++)
		tax[rak[i]]++;
	for (int i = 2; i <= m; i++)
		tax[i] += tax[i - 1];
	for (int i = n; i >= 1; i--)
		sa[tax[rak[tp[i]]]--] = tp[i];	
}

void SA() {
	for (int i = 1; i <= n; i++) {
		rak[i] = c[i];
		tp[i] = i;
	}
	
	m = 1000;
	paixu();
	
	for (int w = 1; w < n; w <<= 1) {
		m = p;
		
		p = 0;
		
		for (int i = n - w + 1; i <= n; i++)
			tp[++p] = i;
		for (int i = 1; i <= n; i++)
			if (sa[i] > w) tp[++p] = sa[i] - w;
		
		paixu();
		
		swap(tp, rak);
		
		p = 1;
		rak[sa[1]] = 1;
		for (int i = 2; i <= n; i++)
			if (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w])
				rak[sa[i]] = p;
			else rak[sa[i]] = ++p;
		
		if (p == n) break;
	}
}

void get_height() {
	int now = 0;
	for (int i = 1; i <= n; i++) rak[sa[i]] = i;
	for (int i = 1; i <= n; i++) {
		if (now) now--;
		for (int j = sa[rak[i] - 1]; c[i + now] == c[j + now]; now++);
		height[rak[i]] = now;
	}
}

bool check(int mid) {
	while (!q.empty()) q.pop();
	memset(in, 0, sizeof(in));
	
	for (int i = 2; i <= n; i++)
		if (height[i] >= mid) {
			if (!in[str[sa[i]]]) {
				q.push(str[sa[i]]);
				in[str[sa[i]]] = 1;
			}
			if (!in[str[sa[i - 1]]]) {
				q.push(str[sa[i - 1]]);
				in[str[sa[i - 1]]] = 1;
			}
		}
		else {
			if (q.size() == t) return 1;
			while (!q.empty()) q.pop();
			memset(in, 0, sizeof(in));
		}
	
	if (q.size() == t) return 1;
	
	return 0;
}

void work() {
	if (t == 1) {
		printf("%d\n", sn);
		
		return ;
	}
	
	int lef = 0, rig = 100, re;
	while (lef <= rig) {
		int mid = (lef + rig) >> 1;
		if (check(mid)) {
			re = mid;
			lef = mid + 1;
		}
		else rig = mid - 1;
	}
	
	printf("%d\n", re);
}

int main() {
	scanf("%d", &T);
	for (int times = 1; times <= T; times++) {
		csh();
		
		scanf("%d", &t);
		
		for (int ii = 1; ii <= t; ii++) {
			scanf("%s", s + 1);
			
			sn = strlen(s + 1);//把所有字符串以及它们翻转的拼接在一起
			for (int i = 1; i <= sn; i++) {
				c[++n] = s[i] - 'A' + 2 * t + 1;
				str[n] = ii;
			}
			c[++n] = ++tmp;//中间用互不相同的字符隔开
			str[n] = ii;
			for (int i = sn; i >= 1; i--) {
				c[++n] = s[i] - 'A' + 2 * t + 1;
				str[n] = ii;
			}
			c[++n] = ++tmp;//中间用互不相同的字符隔开str[n] = ii;
		}
		
		SA();//跑 SA
		
		get_height();//得出 height 数组
		
		work();//二分答案
	}
	
	return 0;
}

标签:子串,sizeof,int,memset,ybt,100001,str,sa,金牌
来源: https://blog.csdn.net/weixin_43346722/article/details/113825983