Loading

2017ICPC青岛 - J.Suffix

题目虽然假了,但是正解真的非常的好...
题目链接
首先由于当前选取方案会受后一个字符串的选取方案影响,于是我们考虑倒着选取。
那么我们如何知道当前选取多少是最优的呢?答案就是在已经获得优解的字符串基础上,试探性地加上当前枚举的字符

就拿题目中的

3
bbb
aaa
ccc

举例

由于我们是倒着枚举,所以先获得了\(c\)的优解。
此时我们枚举\(aaa\),先加上一个\(a\)变成\(ca\)(因为一定要取一个),之后我们的任务其实就是在\(ca\), \(caa\), \(caaa\)里面选取字典序最小的,注意这里其实是倒着的,所以比较的时候也要从尾部开始。如何快速比较两个字符串的字典序大小呢,我们想到了字符串hash + 二分。于是总复杂度就变成了\(O(NlogN)\)

更具体的注释都写在代码里了。

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int maxn = 5e5 + 10;

ull pw[maxn], hs[maxn];
const ull base = 131;
char ans[maxn], s[maxn];
int tot;

ull get(int L, int R) {
    return hs[R] - hs[L-1] * pw[R-L+1];
}

 ///二分比较 [x-mid+1, x] 与 [y-mid+1, y] 的字典序大小
bool check(int cur, int can) {
    int L = 1, R = can, len = 0;
    while (L <= R) {
        int mid = L + R >> 1;
        if (get(cur-mid+1, cur) == get(can-mid+1, can)) {
            L = mid + 1;
            len = max(len, mid);
        }
        else {
            R = mid - 1;
        }
    }
    if (can == len) return 0; ///如果LCP等于can的长度,那么肯定是can小,返回0
    return ans[cur-len] < ans[can-len]; ///返回当前枚举是否比can要小
}

int main() {
    int T; scanf("%d", &T);
    pw[0] = 1;
    for (int i = 1; i < maxn; ++ i)
        pw[i] = pw[i-1] * base;
    while (T--) {
        int n; scanf("%d", &n);
        vector<string> rec;
        rec.push_back(""); ///让下标从1开始
        for (int i = 1; i <= n; ++ i) {
            scanf("%s", s);
            rec.push_back(s);
        }
        tot = 0;
        for (int k = n; k >= 1; -- k) {
            string &u = rec[k]; ///用引用减少数组嵌套增加可读性
            u = " " + u; ///下标从1开始
            int len = (int)u.size()-1;
            ans[++tot] = u[len]; ///不管怎么样都要取一个后缀
            hs[tot] = hs[tot-1] * base + u[len];
            int st = tot, add = 1, can = tot; /// can:最远可达位置, st:此时起始位置, add:增量器
            for (int i = len-1; i >= 1; -- i, ++ add) {
                ///暂时更新,反正如果不满足的话会覆盖的
                hs[st+add] = hs[st+add-1] * base + u[i];
                ans[st+add] = u[i];
                if (check(st+add, can)) ///将当前枚举位置与当前最优位置进行比较
                    can = st + add; ///更优则更新
            }
            tot = can; ///将当前位置更新
        }
        for (int i = tot; i >= 1; i --) ///输出
            printf("%c", ans[i]);
        puts("");
    }
    return 0;
}

posted @ 2021-11-18 20:45  ViKyanite  阅读(43)  评论(0编辑  收藏  举报