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;
}