zoukankan      html  css  js  c++  java
  • 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;
    }
    
    
  • 相关阅读:
    react 给选中的li添加样式
    纯css实现移动端横向滑动列表
    从一个Git仓库转移到另外一个仓库
    create-react-app 创建react项目 多页面应用
    JetBrains出品,一款好用到爆的数据库工具
    gloox环境搭建并运行example(小白教程,有图版本)
    Dubbo服务注册原理
    永久解决 matplotlib 图例中文方块错误
    对称二叉树
    SpringBoot运行原理
  • 原文地址:https://www.cnblogs.com/Vikyanite/p/15574376.html
Copyright © 2011-2022 走看看