https://nanti.jisuanke.com/t/18520
题意:
给你n个字符串,你需要依次从每个字符串选一个后缀拼接起来,问字典序最小的串是什么?
题解:
贪心从后往前看,最后一个串一定选择字典序最小的后缀,然后把这个后缀拼接到第n - 1个串,重复这个步骤就行了。
具体实现:
从后往前遍历,每次找当前串的最小后缀,这个可以对于当前下标和当前最小后缀下标二分+hash找到lcp,看lcp后一个位置的大小,然后更新最小后缀的下标。然后把最小后缀拼接到前一个串即可。
理论时间复杂度跟后缀数组差不多,但后缀数组常数和编码量更大。
代码:
#include <bits/stdc++.h>
#ifdef LOCAL
#define debug(x) cout<<#x<<" = "<<(x)<<endl;
#else
#define debug(x) 1;
#endif
#define chmax(x,y) x=max(x,y)
#define chmin(x,y) x=min(x,y)
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
#define lowbit(x) x&-x
#define mp make_pair
#define pb push_back
#define fir first
#define sec second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int MOD = 1e9 + 7;
const double PI = acos (-1.);
const double eps = 1e-10;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 2e6 + 5;
char s[MAXN];
char *p[MAXN];
int len[MAXN];
#define seed 233
ull hs[MAXN];
ull kpw[MAXN];
ull getHash(int l, int r) {
return hs[r] - hs[l - 1] * kpw[r - l + 1];
}
int main() {
#ifdef LOCAL
freopen ("input.txt", "r", stdin);
#endif
int T;
cin >> T;
kpw[0] = 1;
for (int i = 1; i < MAXN; i++) kpw[i] = kpw[i - 1] * seed;
while(T--) {
int n;
scanf("%d", &n);
int tot = 1000000;
p[0] = s;
for (int i = 1; i <= n; i++) {
scanf("%s", s + tot);
p[i] = s + tot;
tot += (len[i] = strlen(s + tot)) + 1;
}
for (int i = n; i >= 1; i--) {
int l = 0;
for (char * pt = p[i]; *pt; pt++, l++) hs[l + 1] = hs[l] * seed + (*pt) - 'a' + 1;
int pos = len[i] - 1;
for (int j = 0; j < len[i]; j++) {
int ll = 0, rr = l - j;
while(ll <= rr) {
int mid = (ll + rr) >> 1;
if (getHash(pos + 1, pos + mid) == getHash(j + 1, j + mid)) ll = mid + 1;
else rr = mid - 1;
}
int lcp = rr;
if (*(p[i] + pos + lcp) > *(p[i] + j + lcp)) pos = j;
}
for (int j = pos; j < l; j++) *(p[i - 1] + j - pos + len[i - 1]) = *(p[i] + j);
*(p[i - 1] + l - pos + len[i - 1]) = 0;
}
printf("%s\n", p[0]);
}
return 0;
}