传送门
这就是一个板儿题,结果好几种智障操作白给了。
首先用一种后缀数据结构求出本质不同的子串个数,再二分答案。
(我比赛的时候想复杂了,将第\(k\)小转换成了第\(k\)大,但本质上是一样的)
对于当前二分值\(mid\),我们要找所有权值大于等于\(mid\)的子串个数,那么对于SAM上的每一个点,可以要么选这个节点代表的所有子串,要么选择一部分子串。那么再进行一遍二分就行了。
时间复杂度\(O(nlog^2n)\).#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<assert.h>
using namespace std;
#define space putchar(' ')
#define enter puts("")
#define Mem(a, x) memset(a, x, sizeof(a))
#define forE(i, x, y) for(int i = head[x], y; ~i && ((y = e[i].to) >= 0); i = e[i].nxt)
#define In
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxs = 27;
const int maxn = 8e5 + 5;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
char s[maxn];
int n, c[maxs], sum[maxn];
ll K;
struct Sam
{
int tra[maxn << 1][maxs], link[maxn << 1], len[maxn << 1], endp[maxn << 1], cnt, las;
In void init() {link[cnt = las = 0] = -1; Mem(tra[0], 0);}
In void insert(int c, int id)
{
int now = ++cnt, p = las; Mem(tra[now], 0);
len[now] = len[p] + 1, endp[now] = id;
while(~p && !tra[p][c]) tra[p][c] = now, p = link[p];
if(p == -1) link[now] = 0;
else
{
int q = tra[p][c];
if(len[q] == len[p] + 1) link[now] = q;
else
{
int clo = ++cnt;
memcpy(tra[clo], tra[q], sizeof(tra[q]));
len[clo] = len[p] + 1, endp[clo] = endp[q];
link[clo] = link[q]; link[q] = link[now] = clo;
while(~p && tra[p][c] == q) tra[p][c] = clo, p = link[p];
}
}
las = now;
}
int buc[maxn << 1], pos[maxn << 1];
In ll solve()
{
ll ret = 0;
for(int i = 1; i <= cnt; ++i) ret += len[i] - len[link[i]];
return ret;
}
In int ggSum(int L, int R)
{
if(L == 0) return sum[R];
else return sum[R] - sum[L - 1];
}
In int gSum(int now)
{
return ggSum(endp[now] - len[now] + 1, endp[now]);
}
In ll calc(int x)
{
ll ret = 0;
for(int i = 1; i <= cnt; ++i)
{
if(gSum(i) >= x)
{
ll L = len[link[i]] + 1, R = len[i];
while(L < R)
{
int mid = (L + R) >> 1;
if(ggSum(endp[i] - mid + 1, endp[i]) >= x) R = mid;
else L = mid + 1;
}
ret += len[i] - L + 1;
}
}
return ret;
}
}S;
In int solve()
{
S.init();
for(int i = 0; i < n; ++i) S.insert(s[i] - 'a', i);
for(int i = 0; i < n; ++i)
{
if(i) sum[i] = sum[i - 1] + c[s[i] - 'a'];
else sum[i] = c[s[i] - 'a'];
}
ll Sum = S.solve();
K = Sum - K + 1;
if(K < 1) return -1;
int L = 0, R = sum[n - 1];
while(L < R)
{
int mid = (L + R + 1) >> 1;
if(S.calc(mid) >= K) L = mid;
else R = mid - 1;
}
return L;
}
int main()
{
int T = read();
while(T--)
{
n = read(), K = read();
scanf("%s", s);
for(int i = 0; i < 26; ++i) c[i] = read();
write(solve()), enter;
}
return 0;
} 比赛的时候还建出了后缀树,然后进行树上差分,麻烦的很。
|