2017icpc青岛 - Suffix(字符串哈希)

题目

source

题解

首先考虑暴力怎么做。直观感觉就是选择最小的后缀拼起来。但是当前的选择会受到后面字符串的影响。因此,考虑从后往前选择。假设当前选到第\(i\)个串(\(s[i]\)),第\(i+1\)\(n\)后缀拼接的最小字符串为\(t\)。那么如果考虑到当前第\(i\)个串,最优选择就是字符串\(s[i]+t\)的至少包含\(s[i]\)最后一个字符的最小后缀。证明略。

因此问题就转换为快速求一个串的最小后缀,而且要求如果在首部插入字符可以快速地实时更新新串的最小后缀。后缀数组或许可以,但是它是\(O(n\log n)\)求得所有后缀的排序,而这里只需要最小的后缀即可,并且后缀数组不支持更新,每插入一个新字符就要重新求。

因此这里使用字符串哈希求最小后缀。对于后缀\(i\)\(j\)(即它们首字符在串中位置),二分lcp长度,从而找到第一个值不同的位置比较即可。字符串哈希区间判断相等是\(O(1)\),二分时间复杂度\(O(\log n)\)。维护字符串哈希可以将字符串逆序,然后头插变为尾插,这样就可以\(O(1)\)更新。

每次只在遍历新插入的字符串,总时间复杂度为\(O(n\log n)\)

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 1e6 + 10;
const int M = 1e9 + 7;
const double eps = 1e-5;

string ans;
vector<ull> hs;
string s[N];
const int base = 30;
ull pw[N], rpw[N];

inline ll qpow(ll a, ll b, ll m) {
    ll res = 1;
    while(b) {
        if(b & 1) res = (res * a) % m;
        a = (a * a) % m;
        b = b >> 1;
    }
    return res;
}

void insert(string t) {
    reverse(t.begin(), t.end());
    ans += t;
    ull res = 0, cb = pw[hs.size()];
    if(!hs.empty()) res = hs.back();
    for(int i = 0; i < t.size(); i++) {
        res += (t[i] - 'a') * cb % M;
        cb = cb * base % M;
        hs.push_back(res);
    }
    res %= M;
}

ull cal(int l, int r) {
    ull res = hs[r];
    if(l - 1 >= 0) res -= hs[l - 1];
    res = (res + M) * rpw[l] % M;
    return res;
}

bool cmp(int i, int j) { // <
    int len = min(i, j) + 1;
    int l = 1, r = len;
    while(l <= r) {
        int mid = (l + r) / 2;
        if(cal(i - mid + 1, i) == cal(j - mid + 1, j)) l = mid + 1;
        else r = mid - 1;
    }
    if(l > len) return i < j;
    return ans[i - l + 1] < ans[j - l + 1];
}

int main() {
    IOS;
    rpw[0] = pw[0] = 1;
    for(int i = 1; i < N; i++) {
        pw[i] = pw[i - 1] * base % M;
        rpw[i] = rpw[i - 1] * qpow(base, M - 2, M) % M;
    }
    int t;
    cin >> t;
    while(t--) {
        ans.clear();
        hs.clear();
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> s[i];
        for(int i = n; i >= 1; i--) {
            int tar = ans.size();
            insert(s[i]);
            for(int j = tar + 1; j < ans.size(); j++) {
                if(cmp(j, tar)) {
                    tar = j;
                }
            }
            ans.erase(ans.begin() + tar + 1, ans.end());
            hs.erase(hs.begin() + tar + 1, hs.end());
        }
        reverse(ans.begin(), ans.end());
        cout << ans << endl;
    }
}
posted @ 2021-10-27 21:43  limil  阅读(91)  评论(0编辑  收藏  举报