UVALive - 8488 Suffix(二分+hash)

思路

我们可以首先取最后一个字符串字典序最小的后缀,然后把它接到前一个串的后面,再求这个串字典序最小的后缀,不断重复以上操作我们就能得到题目需要的字符串。其正确性可以用反证法证明:假设我们最后一个字符串不取字典序最小的后缀,那么我们一定能通过替换最小的后缀来得到一个更小的字符串,与原串是字典序最小的字符串矛盾。但这样的时间复杂度是O(n^2)的(字符串比较O(n)),我们期望的是一个O(nlogn)的算法。注意到我们取的过程是从最后一个字符慢慢往前走,如果我们保存每个位置的hash值(这里hash的方法需要是Hash=Hash*seed+letter),然后通过现在的hash值减之前的hash值乘以偏移量,那么我们就能得到一段前缀的hash值,也就是可以O(1)的比较一段前缀是否相等。因此我们可以想到二分前缀的长度(显然具有单调性),通过上述hash的方法来得到两个字符串最长的LCP。知道了两个字符串最长的公共前缀后,我们只要比较最长公共前缀后面的第一个字符即可比较两个字符串的大小。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define INTINF 0x7fffffff
#define LLINF 0x7fffffffffffffff
#define ms(a,val) memset((a),(val),(sizeof(a)))
#define sqr(x) ((x)*(x))
#define all(x) (x).begin(),(x).end()
using namespace std;
ll quickpow(ll a,ll b,ll MOD){ll ans=1;a%=MOD;while(b){if(b&1ll)ans=ans*a%MOD;a=a*a%MOD,b>>=1;}return ans;}
ll gcd(ll a,ll b){return a%b==0?b:gcd(b,a%b);}
//head

const ll MAXN=500005;
const ll seed=131;
string str[MAXN];
char ans[MAXN];
ull len[MAXN],hashVal[MAXN],po[MAXN];
bool check(int now,int pos){
int lb=0,ub=pos;
while(lb<=ub){
int mid=(lb+ub)>>1;
ull num1=hashVal[now]-hashVal[now-mid]*po[mid],num2=hashVal[pos]-hashVal[pos-mid]*po[mid];
if(num1==num2)lb=mid+1;
else ub=mid-1;
}
if(lb==pos+1)return false;
else{
return ans[now-ub-1]<ans[pos-ub-1];
}
}

int main() {
#ifndef ONLINE_JUDGE
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
#endif // ONLINE_JUDGE
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
po[0]=1;
for(int i=1;i<MAXN;i++)po[i]=po[i-1]*seed;
int T;
cin>>T;
while(T--){
int n;
ms(hashVal,0);
cin>>n;
for(int i=0;i<n;i++){
cin>>str[i];
len[i]=str[i].length();
}
int pans=0;
for(int i=n-1;i>=0;i--){
ans[pans]=str[i][len[i]-1];
hashVal[pans+1]=hashVal[pans]*seed+str[i][len[i]-1];
pans++;
int pnow=pans;
for(int j=len[i]-2;j>=0;j--){
ans[pnow]=str[i][j];
hashVal[pnow+1]=hashVal[pnow]*seed+str[i][j];
pnow++;
if(check(pnow,pans)){
pans=pnow;
}
}
}
for(int i=pans-1;i>=0;i--)cout<<ans[i];
cout<<"\n";
}
return 0;
}