2016EC Final F.Mr. Panda and Fantastic Beasts

题目大意

\(T(1\leq T\leq42)\)组数据,给定\(n(2\leq n\leq 50000)\)个字符串\(S_{i}(n\leq\sum_{i=1}^{n}S_{i}\leq 250000\),所有\(T\)\(\sum S_{i}\leq 3 \times 10^6)\)求出一个最短的字符串,其仅为第\(1\)个字符串的字串(有多个长度相同的则求其中字典序最小的)。

思路

我们考虑用一个特殊字符将所有字符串串成一个串,求出该串的\(sa[\space]\)\(lcp[\space]\),我们考虑从第一个串范围内开始的每一个后缀\(sa[i]\),找出在后缀数组中的前一个以及后一个起点不在第一个串内的后缀\(pred[i],succ[i]\)(更远的后缀与其的\(lcp\)不会更大,所以仅需一前一后最近的两个即可),我们可以对\(lcp[\space]\)\(rmq\)来求出\(max(lcp(sa[i],pred[i]),lcp(sa[i],succ[i]))\),如果最长公共前缀为\(n\),说明以\(i\)这个位置为起点的第一个串长为\(1\sim n\)的字串都在其他串中出现过,所以以\(i\)这个位置为起点的长为\(n+1\)的第一个串的字串就是最短的以\(i\)这个位置为起点的满足要求的串(注意判断一下这个串的合法性,可能超出第一个串的范围),我们对后缀数组从前往后遍历,不断更新答案,于是可以求出在最短的情况下字典序最小的串。

代码

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
#define all(x) x.begin(),x.end()
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 500010;

int T, N, cnt = 0;
string SS, t;
int n, k;
int rk[maxn], tmp[maxn], sa[maxn], lcp[maxn], flen, pred[maxn], succ[maxn];

bool compare_sa(int i, int j)
{
	if (rk[i] != rk[j])
		return rk[i] < rk[j];
	else
	{
		int ri = i + k <= n ? rk[i + k] : -1;
		int rj = j + k <= n ? rk[j + k] : -1;
		return ri < rj;
	}
}

void construct_sa(string S, int* sa)
{
	n = S.length();
	for (int i = 0; i <= n; i++)
	{
		sa[i] = i;
		rk[i] = i < n ? S[i] : -1;
	}
	for (k = 1; k <= n; k *= 2)
	{
		sort(sa, sa + n + 1, compare_sa);
		tmp[sa[0]] = 0;
		for (int i = 1; i <= n; i++)
			tmp[sa[i]] = tmp[sa[i - 1]] + (compare_sa(sa[i - 1], sa[i]) ? 1 : 0);
		for (int i = 0; i <= n; i++)
			rk[i] = tmp[i];
	}
}

void construct_lcp(string S, int* sa, int* lcp)
{
	int n = S.length();
	for (int i = 0; i <= n; i++)
		rk[sa[i]] = i;
	int h = 0;
	lcp[0] = 0;
	for (int i = 0; i < n; i++)
	{
		int j = sa[rk[i] - 1];
		if (h > 0)
			h--;
		for (; j + h < n && i + h < n; h++)
		{
			if (S[j + h] != S[i + h])
				break;
		}

		lcp[rk[i] - 1] = h;
	}
}

int ST[maxn][30];

void LCP_init(int n)
{
	for (int i = 0; i < n; i++)
		ST[i][0] = lcp[i];
	for (int j = 1; (1 << j) <= n; j++)
	{
		for (int i = 0; i + (1 << j) - 1 < n; i++)
			ST[i][j] = min(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
	}
}

int LCP(int l, int r)//[l,r)
{
	if (l >= r)
		return 0;
	int k = floor(log2(r - l));

	return min(ST[l][k], ST[r - (1 << k)][k]);
}

void solve()
{
	int len = SS.length(), ans = -1, sou = -1;
	construct_sa(SS, sa);
	construct_lcp(SS, sa, lcp);
	LCP_init(len);
	int temp = 0;
	for (int i = 1; i <= len; i++)
	{
		if (sa[i] >= flen)
			temp = i;
		else
			pred[i] = temp;//前一个最近的从第一个字串外开始的后缀
	}
	temp = 0;
	for (int i = len; i >= 1; i--)
	{
		if (sa[i] >= flen)
			temp = i;
		else
			succ[i] = temp;//后一个最近的从第一个字串外开始的后缀
	}
	for (int i = 1; i <= len; i++)
	{
		int mx = 0;
		if (sa[i] < flen)
		{
			if (pred[i])
				mx = max(mx, LCP(pred[i], i));
			if (succ[i])
				mx = max(mx, LCP(i, succ[i]));
			if ((ans == -1 || ans > mx + 1) && sa[i] + mx + 1 <= flen)
				ans = mx + 1, sou = sa[i];
		}
	}
	if (ans == -1)
		cout << "Case #" << cnt << ": Impossible" << endl;
	else
	{
		cout << "Case #" << cnt << ": ";
		for (int i = sou; i < sou + ans; i++)
			cout << SS[i];
		cout << endl;
	}
}

int main()
{
	IOS;
	cin >> T;
	while (T--)
	{
		cnt++;
		memset(pred, 0, sizeof(pred));
		memset(succ, 0, sizeof(succ));
		cin >> N >> SS;
		flen = SS.size();
		for (int i = 2; i <= N; i++)
		{
			cin >> t;
			SS += '#' + t;
		}
		solve();
	}

	return 0;
}
posted @ 2022-03-02 09:27  Prgl  阅读(39)  评论(0)    收藏  举报