题目:http://www.lydsy.com/JudgeOnline/problem.php?id=4278
思路:学习了后缀数组。。。做了这道模板题。。。
考虑按位贪心,每次作出最优决策,那么我们每次比较两个后缀的
rank
值,取较小的即可。
关键是怎么证明呢?
考虑双归纳法,设命题
P(n,m)
当两个串的长为
n,m
时上述结论成立,当
n<m
时我们将其后补为
inf
,这样首先结论对
P(1,m)
和
P(n,1)
成立
那么当
n,m>1
时,假设指针分别在
i,j
,
lcp(i,j)=l,s1[i+l]<s2[j+l]
,如果目前直接取
s1
,则命题成立,否则注意到方案的对称性,将方案对称总使得
s1[i+l]
比
s2[i+l]
先取,那么假设
s1
取到
i+l
时
s2
取到
k(k<j+l)
,那么这时就变成了一个子问题,我们就可以归纳证明了。
另外,值得一提的是,这道题有一个相似的变形,就是在一个字符串两边取,字典序最小,其方法和这个一样,为什么呢?
首先考虑枚举最后一个取得字符位置
loc
,这样就把原序列分为两个序列,成为了上面的问题,然而注意到,这里的后缀其
lcp
可能会跨过
loc
,这样上面的命题就不符合了,没关系,我们注意到另一个性质,就是如果一个串两端的
lcp
为
l
,那么分割点一定不可能在两端那些
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define N 400010
#define inf 1001
using namespace std;
int c,l,n,m,sa[N],Rank[N],wa[N],wb[N],was[N],wv[N],r[N];
void init(){
scanf("%d",&n);
for (int i = 0;i < n; ++i) scanf("%d",&r[i]);
r[n] = inf;
scanf("%d",&m);
for (int i = n + 1;i <= n + m; ++i) scanf("%d",&r[i]);
r[n + m + 1] = inf;
l = n + m + 2;
c = inf + 1;
}
void da(){
int i,j,p,*x = wa,*y = wb,*t;
for (i = 0;i < c; ++i) was[i] = 0;
for (i = 0;i < l; ++i) was[x[i] = r[i]]++;
for (i = 1;i < c; ++i) was[i] += was[i - 1];
for (i = l - 1;i >= 0; --i) sa[--was[r[i]]] = i;
for (j = 1,p = 1;p < l;j <<= 1,c = p){
for (i = l - j,p = 0;i < l; ++i) y[p++] = i;
for (i = 0;i < l;++i)
if (sa[i] >= j) y[p++] = sa[i] - j;
for (i = 0;i < l; ++i) wv[i] = x[y[i]];
for (i = 0;i < c; ++i) was[i] = 0;
for (i = 0;i < l; ++i) was[wv[i]]++;
for (i = 1;i < c; ++i) was[i] += was[i - 1];
for (i = l - 1;i >= 0; --i) sa[--was[wv[i]]] = y[i];
for (t = x,x = y,y = t,p = 1,i = 1,x[sa[0]] = 0;i < l; ++i){
int a = sa[i],b = sa[i - 1];
x[a] = (a + j < l&&b + j < l&&y[a] == y[b]&&y[a + j] == y[b + j]) ? p - 1 : p++;
}
}
for (i = 0;i < l; ++i) Rank[sa[i]] = i;
}
void DO_IT(){
da();
int i = 0,j = n + 1;
for (int k = 1;k <= n + m; ++k)
if (Rank[i] < Rank[j]) printf("%d ",r[i++]);
else printf("%d ",r[j++]);
}
int main(){
init();
DO_IT();
return 0;
}
总结: