EOJ 3424 二分+字符串Hash

EOJ 3424 二分+字符串Hash

题目大意

列出n个字符串,依次从每个字符串中获取一个后缀(不得不取),再依次从上到下连接这些后缀形成新的字符串,问:这样能组合成的字典序最小的字符串是什么。题目在此

思路

题目给的数据范围非常奇妙,单个字符串长度小于50000,保证所有字符串加起来不超过50000。很自然地想到将所有字符串统一起来考虑。
如果我们从最后一位开始枚举添加进答案的字符串前缀,那么时间复杂度约为O(n*(判断的复杂度))。每次枚举一个字符串添加进答案的前缀中,就判断一下新形成的字符串与之前既定的字符串的字典序,如果新形成的串的字典序小,那么将新形成的串当成既定的串。

知识点——字符串Hash比较两字符串字典序

众所周知,字典序比较的传统方法是O(n)比较,如果我们使用传统方法来比较,整个题目的复杂度为O(n2),妥妥地TLE了。那么有什么更加巧妙快速地方法来比较两个串的字典序呢?答案就是字符串Hash。字符串Hash常用与比较两字符串是否相同,但是比较字典序时,它同时也能够派上用处。
比如aaaabaabba两个串,使用传统方法的话,它们的字典序是到第三位出比较结果,因为第三位之前的前缀都是一模一样的。如果我们使用二分法加字符串Hash来查找两个字符串的最长公共前缀长度,字符串Hash本身只需要O(1)的复杂度,那么所需的时间为O(logn)。

AC代码

#include <bits/stdc++.h>

typedef long long ll;
typedef unsigned long long ull;
using namespace std;


#define CHAR_NUM 30
#ifdef ACM_LOCAL
const int NUM = 100;
#endif
#ifndef ACM_LOCAL
const int NUM = 500100;
#endif

string strs[NUM];
char ans[NUM];

ull ha[NUM], fac[NUM];
ull seed = 131;

/**
 *获取哈希值
 */
ull getStr(int l, int r) {
    l--;
    if (l == -1)
        return ha[r];
    else
        return ha[r] - ha[l] * fac[r - l];
}

int main() {
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    fac[0] = 1;
    for (int i = 1; i < NUM; i++)
        fac[i] = fac[i - 1] * seed;

    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> strs[i];

    int last = -1;
    for (int i = n - 1; i >= 0; i--) {
        int size = strs[i].size();
        last++;
        if (last == 0)
            ha[last] = strs[i].at(size - 1);
        else
            ha[last] = ha[last - 1] * seed + strs[i].at(size - 1);
        ans[last] = strs[i].at(size - 1);

        int tmp = last;
        for (int j = size - 2; j >= 0; j--) {
            last++;
            ha[last] = ha[last - 1] * seed + strs[i].at(j);
            ans[last] = strs[i].at(j);

            //cmp
            int l = 0, r = tmp, mid;
            int res = r + 1;
            while (l <= r) {
                mid = (l + r) / 2;
                if (getStr(mid, tmp) == getStr(last - (tmp - mid), last)) {
                    res = mid;
                    r = mid - 1;
                } else
                    l = mid + 1;
            }

            if (ans[last - (tmp - res + 1)] < ans[res - 1])
                tmp = last;
        }
        last = tmp;
    }

    for (int i = last; i >= 0; i--)
        cout << ans[i];
    cout << endl;

    return 0;
}

总结

字符串Hash是一个非常好用的方法,能够处理大部分的小规模字符串问题。这道题是本人遇到的第一个感觉很厉害的字符串Hash题目,看了网上大佬们的题解才解出了这个题目,颠覆了我对字符串Hash的看法,在此做个笔记。
若有说得不明确或是错误的地方,欢迎评论区指正,谢谢~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值