2024内部排位赛补题(SAM)

2024内部排位赛补题1

在这里插入图片描述
题意:
给定一个小写字母组成的字符串,每个字符都有一个权值 c i c_i ci,求权值为第 k k k小的本质不同子串的权值。

题解:
对于这种涉及子串的问题,我们可以考虑到使用后缀自动机去求解。
考虑一个子串,其后缀的权值必然小于等于该子串的权值,利用这个性质我们可以遍历后缀自动机中所有的 e n d p o s endpos endpos集合,对于每一个集合来说,其后缀的权值满足在长度上的连续性,因此我们可以二分权值,然后对于每个 e n d p o s endpos endpos集合再二分其长度,找到最长的后缀,累加每个集合的后缀个数,判断与 k k k的关系,继续二分权值,直到找到答案即可。

#include<bits/stdc++.h>
using namespace std;
// #define int long long
#define endl '\n'
typedef pair<int, int> pll;
constexpr int N = 2e5 + 7;
int n;
long long k;
int sum[N];
int w[26];
struct SAM
{
    int len[N];//某个节点作为末尾位置所对应的最长子串
    int f[N];//parent树中指向子节点所对应的唯一父节点
    int endpos[N];//某个节点在原串中的所对应的位置(从0开始)
    long long cnt[N];//dfs前表示该节点出现一次,dfs后表示该点对应的子串的数量
    int s[N][26];//后缀自动机中的下一个节点
    int tot = 1, last = 1;
    void init()
    {
        tot = last = 1;
        memset(endpos, 0, sizeof endpos);
        memset(s, 0 ,sizeof s);
    }
    void insert(int x, int idx)
    {
        int p = last, np = last = ++tot;
        cnt[tot] = 1;
        len[np] = len[p] + 1;
        endpos[np] = idx;
        for (; p && !s[p][x]; p = f[p])
            s[p][x] = np;
        if (!p)
            f[np] = 1;
        else
        {
            int copy = s[p][x];
            if (len[copy] == len[p] + 1)
                f[np] = copy;
            else
            {
                int ncopy = ++tot;
                memcpy(s[ncopy], s[copy], sizeof s[0]);
                f[ncopy] = f[copy];
                len[ncopy] = len[p] + 1;
                f[copy] = f[np] = ncopy;
                endpos[ncopy] = endpos[copy];
                for (; p && s[p][x] == copy; p = f[p])
                    s[p][x] = ncopy;
            }
        }
    }
} sam;
bool check(int c)
{
    long long cnt = 0;
    for (int i = 1; i <= sam.tot; i++)
    {
        int pos = sam.endpos[i], l = pos - sam.len[i] + 1, r = pos - sam.len[sam.f[i]];
        while(l < r)//二分长度
        {
            int mid = l + r >> 1;
            if(sum[pos] - sum[mid - 1] <= c)r = mid;
            else l = mid + 1;
        }
        if(sum[pos] - sum[l - 1] <= c)cnt += pos - sam.len[sam.f[i]] - l + 1;//累加计数
    }
    return (cnt >= k);
}
void solve()
{
    sam.init();
    cin >> n >> k;
    string s;
    cin >> s;
    s = '.' + s;
    for (int i = 0; i < 26; i++)cin >> w[i];
    for (int i = 1; i <= n; i++)
    {
        sum[i] += sum[i - 1] + w[s[i] - 'a'];
        sam.insert(s[i] - 'a', i);
    }
    int l = 1, r = sum[n];
    // cerr << l << " " << r << endl;
    while(l < r)//二分权值
    {
        int mid = l + r >> 1;
        if(check(mid))r = mid;
        else l = mid + 1;
    }
    if(check(l))cout << l << endl;
    else cout << -1 << endl;
    for (int i = 1; i <= n; i++)sum[i] = 0;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值