bzoj4310: 跳蚤 后缀自动机+Hash字符串比较

本文介绍了一道关于字符串处理的算法题目,通过后缀自动机和哈希技巧找到最优的字符串划分方式,使得最终得到的‘魔力串’字典序最小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

bzoj4310: 跳蚤

Description

很久很久以前,森林里住着一群跳蚤。一天,跳蚤国王得到了一个神秘的字符串,它想进行研究。首先,他会把串
分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个,并在选出来的 k
个子串中选择字典序最大的那一个。他称其为“魔力串”。现在他想找一个最优的分法让“魔力串”字典序 最小(题目有误)

Input

第一行一个整数 k,K<=15
接下来一个长度不超过 10^5 的字符串 S。

Output

输出一行,表示字典序最大的“魔力串”。

Sample Input

2
ababa

Sample Output

ba
//解释:
分成aba和ba两个串,其中字典序最大的子串为ba

分析

最大值最小肯定是二分答案
二分答案是第mid大的字符串
然后贪心
从尾扫到头一直加字符直到这个字符串比我们二分的答案更大
然后切一段
考虑到切的段越少说明我们的字符串越容易让某段的字符满足条件,那么我们就可以切更小的字符串。
那么剩下我们要做的就是兹瓷线性第k大和log的字符串比较
第k大我用的是后缀自动机,字符串比较我用的是Hash
因为是第一次写所以发一个算法流程:
字符串比较和找最长公共前缀是一个操作。
我们从尾到头秦九韶(用字符串的离散后字符做系数),然后一段子串的Hash值就是子串头的秦九韶的值减去子串尾的值承上随机数种子的n次方(相当于%吧)
二分答案即可。
听说这题用后缀数组有个很神的办法
然而我不知道~~

代码

比较烦,码农日常。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
const int N = 4e5+50;
const int sand = 13331;
int fa[N], len[N], ch[N][26], s[N], str[N], c[N], q[N], last, sz, top, K;
long long ans, sum[N], val[N];
unsigned long long h[N], sh[N], bin[N];

void Sam_Extend(int c) {
    int p = last, np = last = ++sz;
    len[np] = len[p] + 1; val[np] = 1;
    for(;p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
    if(!p) fa[np] = 1;
    else {
        int q = ch[p][c];
        if(len[q] == len[p] + 1) fa[np] = q;
        else {
            int nq = ++sz; len[nq] = len[p] + 1;
            memcpy(ch[nq], ch[q], sizeof(ch[q]));
            fa[nq] = fa[q];
            fa[q] = fa[np] = nq;
            for(;ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
        }
    }
}

void Sam_Build() {
    char ch = getchar(); int x = 0; last = ++sz;
    while(ch < 'a' || ch > 'z') ch = getchar();
    for(;ch >= 'a' && ch <= 'z'; ch = getchar()) Sam_Extend(s[++x] = ch - 'a'); 
    s[0] = x;
    for(int i = s[0]; i; --i) h[i] = h[i + 1] * sand + s[i];
    bin[0] = 1; for(int i = 1;i <= s[0]; ++i) bin[i] = bin[i - 1] * sand;
} 

void Sam_Pre() {
    for(int i = 1;i <= sz; ++i) ++c[len[i]];
    for(int i = 1;i <= s[0]; ++i) c[i] += c[i - 1];
    for(int i = sz; i; --i) q[c[len[i]]--] = i;
    for(int i = sz; i; --i) val[i] = 1;
    val[1] = 0;
    for(int i = sz; i; --i) {
        int t = q[i]; sum[t] = val[t];
        for(int c = 0;c < 26; ++c)
            sum[t] += sum[ch[t][c]];
    }
}

void Sam_Kth(int u, long long k) {
    if(k <= val[u]) return;
    k -= val[u]; int v;
    for(int i = 0;i < 26; ++i) 
    if(v = ch[u][i]) {
        if(k <= sum[v]) {
            str[++top] = i;
            Sam_Kth(v, k);
            return ;
        }
        k -= sum[v];
    }
}
unsigned long long hashs(int L, int R) {return h[L] - h[R + 1] * bin[R - L + 1];}
unsigned long long hashc(int L, int R) {return sh[L] - sh[R + 1] * bin[R - L + 1];}

bool cmp(int L, int R) {
    if(str[1] < s[L]) return false;
    if(str[1] > s[L]) return true;
    int l = 1, r = min(top, R - L + 1), ans;
    while(l <= r) {
        int mid = l + r >> 1;
        if(hashc(1, mid) == hashs(L, L + mid - 1)) {
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    } ++ans;
    if(ans > R - L + 1) return true;
    if(ans > top) return false;
    if(str[ans] < s[L + ans - 1]) return false;
    return true;
}

bool check() {
    int ret = 0;
    for(int i = s[0], p; i; i = p) {
        p = i;
        while(p && cmp(p, i)) --p;
        if(p == i) return false;
        ++ret;
    }
    return ret <= K;
}

void Binary() {
    long long L = 1, R = sum[1];
    while(L <= R) {
        long long mid = L + R >> 1;
        top = 0; Sam_Kth(1, mid); sh[top + 1] = 0;
        for(int i = top; i; --i) sh[i] = sh[i + 1] * sand + str[i];
        if(check()) {
            ans = mid; 
            R = mid - 1;
        }
        else L = mid + 1;
    }
}

int main() {
    scanf("%d", &K);
    Sam_Build();
    Sam_Pre();
    Binary();
    top = 0; Sam_Kth(1, ans);
    for(int i = 1;i <= top; ++i) putchar(str[i] + 'a');
    putchar('\n');
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值