题目需要求第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;
}
}