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;
}

浙公网安备 33010602011771号