Display Substring -hdu6988 2021杭电多校 -SAM+二分

题目需要求第k小价值的子串价值,和一个经典题型很相似,所以应该能想到用SAM/SA做。
这道题我们首先需要想到二分查找分数,这个过程使用二分答案,在里面check时套一个SAM,这个SAM就是用来获取所有子串信息的,在SAM内遍历所有节点也就是所有endpos,因为子串长度和价值是成正比的,所以在每个点上二分长度,统计一共有几个子串满足返回与k比较的结果。
要注意用pos数组来对应自动机节点在字符串上位置,才可以进行前缀和操作。
to数组主要是优化dfs过程,否则会tle.

//
// Created by acer on 2021/2/16.
//
//判断子串,不同子串个数,所有子串字典序第i大,最长公共子串

#include "bits/stdc++.h"

#define mem(x, i) memset(x,i,sizeof(x))
using namespace std;
const int MAXN = 6e5 + 10;
char s[MAXN];
int len[MAXN << 1];
int ch[MAXN << 1][27];
int fa[MAXN << 1];
int last = 1;
int tot = 1;
int p;
int sum[MAXN];
int pos[MAXN << 1];
int val[MAXN];
vector<int> to[MAXN];

int add(int c) {
    
    
    p = last;
    last = ++tot;
    int np = last;
    len[np] = len[p] + 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 = ++tot;
            memcpy(ch[nq], ch[q], sizeof(ch[nq]));
            fa[nq] = fa[q];
            len[nq] = len[p] + 1;
            fa[np] = fa[q] = nq;
            for (; p && ch[p][c] == q; p = fa[p]) {
    
    
                ch[p][c] = nq;
            }
        }
    }
    return last;
}

long long n, k;

int check(int x) {
    
    
    long long cnt = 0;
    for (int i = 1; i <= tot; ++i) {
    
           //对于每一个endpos处理,那么就能遍历所有子串
        int L = pos[i] - len[i] + 1;         //左界
        int R = pos[i] - len[fa[i]];          //除去本质相同的子串的右界
        while (L <= R) {
    
    
            int mid = (L + R) >> 1;
            if (sum[pos[i]] - sum[mid - 1] <= x) R = mid - 1;
            else L = mid + 1;
        }
        if (sum[pos[i]] - sum[L - 1] <= x) cnt += pos[i] - len[fa[i]] - L + 1;
    }
    return cnt >= k;
}

void dfs(int now) {
    
       //处理主链外节点,节点串的长度等于节点连的下一个点的长度
    for (auto v : to[now]) {
    
    
        dfs(v);
    }
    if (pos[now] == 0 && !to[now].empty()) {
    
    
        pos[now] = pos[to[now][0]];
    }
}

void init() {
    
    
    memset(len, 0, (n * 2 +10)* sizeof (int));
    memset(ch, 0, (n * 2 + 10) * 27 * sizeof(int));
    memset(pos, 0, (n * 2 + 10) * sizeof(int));
    for (int i = 1; i <= n * 2 + 10; ++i) {
    
    
        to[i].clear();
    }
    fa[1] = 0;
    last = 1;
    tot = 1;
    sum[0] = 0;
}

int main() {
    
    
    ios::sync_with_stdio(0);
    int T;cin >> T;
    while (T--) {
    
    
        cin >> n >> k;
        cin >> (s + 1);int le = strlen(s + 1);
        init();
        for (int i = 0; i < 26; ++i)
            cin >> val[i];
        for (int i = 1; i <= le; ++i) {
    
    
            pos[add(s[i] - 'a')] = i;          //pos将节点与节点在字符串中位置联系起来,而自动机上的子串也是连续的,故方便进行串上的前缀和。
            sum[i] = sum[i - 1] + val[s[i] - 'a'];
        }
        for (int i = 2; i <= tot; ++i) {
    
    
            to[fa[i]].push_back(i);
        }
        dfs(1);
//        for (int i = 2; i <= tot; ++i) {
    
    
//            cout << pos[i] << ' ';
//        }
        if (check(le * 200) == 0) {
    
    
            cout << -1 << endl;
            continue;
        }
        int l = 0, r = le * 200;
        while (l < r) {
    
    
            int mid = (l + r) >> 1;
            if (check(mid)) {
    
    
                r = mid;
            } else {
    
    
                l = mid + 1;
            }
        }
        cout << l  << endl;

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45509601/article/details/119272969