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;
    }
    
    
  • 相关阅读:
    OpenJDK源码研究笔记(十二):JDBC中的元数据,数据库元数据(DatabaseMetaData),参数元数据(ParameterMetaData),结果集元数据(ResultSetMetaDa
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
  • 原文地址:https://www.cnblogs.com/Vikyanite/p/15574376.html
Copyright © 2011-2022 走看看