2016 ACM-ICPC China Finals #F Mr. Panda and Fantastic Beasts
题目链接\(\newcommand{\LCP}{\mathrm{LCP}}\newcommand{\suf}{\mathrm{suf}}\)
题意
给定 \(n\) 个字符串 \(s_1, s_2, \dots, s_n\),求只在 \(s_1\) 中出现过的最短子串,若有多解,输出字典序最小的。
分析
为了方便, 称只在 \(s_1\) 中出现过的子串为「特殊子串」,记「字符串 \(s\) 是字符串 \(t\) 的子串」作 $ s \sqsubseteq t$ 。
引理 1
若 \(s'\) 是特殊子串,若字符串 \(s\) 满足 \(s \sqsubseteq s_1\) 且 \(s' \sqsubseteq s\),那么 \(s\) 也是特殊子串。
因此可二分答案。
问题化为: 判断是否存在长为 \(x~(x \le |s_1|)\) 的特殊子串.
用后缀数组解决上述判定问题
- 将 \(s_1, s_2, \dots, s_n\) 用 未在原串中出现过 且 各不相同 的字符链接成一个长串 \(s\) , 构造 \(s\) 的后缀数组 \(\suf_1, \suf_2, \dots, \suf_i, \dots\) 和 height 数组 \(h_1, h_2, \dots, h_i, \dots\) .
- 寻找后缀数组中满足如下条件的连续段 \(\suf_i, \suf_{i+1}, \dots, \suf_j, \dots, \suf_{i+k-1}\).
- \(\suf_j\) 起始于 \(s_1\) 中
- \(|\suf_j \sqcap s_1| \ge x\)
- \(|\LCP(\suf_{j_1}, \suf_{j_2})| \ge x\), \(\LCP(s, t)\) 指字符串 \(s, t\) 的最长公共前缀 (Longest Common Prefix)
- \(h_i<x\) 且 \(h_{i+k}<x\)
存在长为 \(x\) 的特殊子串 \(\Longleftrightarrow\) 存在上述连续段
Implementation
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
char s[N];
int a[N];
int suf[N], suf2[N], rk[N], cnt[N], h[N];
void SA(int *s, int n, int m, int *suf, int *suf2, int *rk, int *cnt){
for(int i=0; i<m; i++)
cnt[i]=0;
for(int i=0; i<n; i++)
cnt[rk[i]=s[i]]++;
for(int i=1; i<m; i++)
cnt[i]+=cnt[i-1];
for(int i=n-1; i>=0; i--)
suf[--cnt[rk[i]]]=i;
for(int len=1; len<n; len<<=1){
int tail=0;
for(int i=n-len; i<n; i++)
suf2[tail++]=i;
for(int i=0; i<n; i++)
if(suf[i]>=len) suf2[tail++]=suf[i]-len;
for(int i=0; i<m; i++)
cnt[i]=0;
for(int i=0; i<n; i++)
cnt[rk[i]]++;
for(int i=1; i<m; i++)
cnt[i]+=cnt[i-1];
for(int i=n-1; i>=0; i--)
suf[--cnt[rk[suf2[i]]]]=suf2[i];
swap(suf2, rk);
auto same=[suf2, len](int i, int j){
return suf2[i]==suf2[j] && suf2[i+len]==suf2[j+len];
};
rk[suf[0]]=0;
for(int i=1; i<n; i++)
rk[suf[i]]=rk[suf[i-1]]+!same(suf[i], suf[i-1]);
m=rk[suf[n-1]]+1;
if(m==n) break;
}
}
void calc(int *a, int *suf, int n, int *rk, int *h){
for(int i=0; i<n; i++)
rk[suf[i]]=i;
for(int i=0, lcp=0; i<n-1; i++){
if(lcp) --lcp;
for(int j=suf[rk[i]-1]; a[j+lcp]==a[i+lcp]; lcp++);
h[rk[i]]=lcp;
}
}
int ok(int x, int len0, int len, int *h){
//two-pointers
for(int i=1; i<=len; i++){ //tricky
if(suf[i]<len0 && suf[i]+x <= len0 && h[i]<x){ //error-prone
for(++i; i<=len && h[i]>=x && suf[i]<len0; i++);
if(i>len || h[i]<x){
assert(suf[i-1]>=0);
return suf[i-1];
}
}
}
return -1;
}
int main(){
int T;
cin>>T;
for(int cas=1; cas<=T; cas++){
printf("Case #%d: ", cas);
int n;
cin>>n;
cin>>s;
int len0=strlen(s), len=len0;
for(int i=1; i<n; i++){
s[len++]='#';
cin>>(s+len);
len+=strlen(s+len);
}
int m=256;
for(int i=0; i<=len; i++){
if(s[i]=='#') a[i]=m++;
else a[i]=s[i];
}
SA(a, len+1, m, suf, suf2, rk, cnt);
calc(a, suf, len+1, rk, h);
int l=0, r=len0+1, res=-1;
for(; l+1<r; ){
int mid=(l+r)>>1;
int tmp=ok(mid, len0, len, h);
if(tmp!=-1){
res=tmp;
r=mid;
}
else l=mid;
}
if(r>len0) puts("Impossible");
else{
for(int i=res; i<res+r; i++)
putchar(s[i]);
puts("");
}
}
return 0;
}