题目大意
给出长度为 nn 的字符串序列,第 个字符串有价值 cici
要求从中选出允许不连续的子序列,使得前一个是后一个的子串,同时权值和最大。
数据范围
1≤n≤2×104,1≤∑len≤3×1051≤n≤2×104,1≤∑len≤3×105
思路
考虑在 failfail 树上 DP ,在序列上从后往前面 DP 对于当前 ii 这个字符串若允许放在 前面,那么 ii 在 树上的末尾节点的子树中一定有 jj 的节点,也就是子树查询 DP 最大值,这个可以用线段树维护
由于总长度不超过 所以我们在修改的时候可以暴力从 trietrie 树上走下去,将经过的链的权值修改一遍
题目中并没有给权值的范围,所以 infinf 尽量往大的开
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using std :: vector;
template <typename Tp>Tp Max(const Tp &a, const Tp &b) {return a > b ? a : b;}
template <typename Tp>Tp Min(const Tp &a, const Tp &b) {return a < b ? a : b;}
template <typename Tp>void Read(Tp &x) {
Tp in = 0, f = 1; char ch = getchar();
while(ch<'0' || ch>'9') {if(ch=='-') f = -1; ch = getchar();}
while(ch>='0' && ch<='9') {in = in*10+ch-'0'; ch = getchar();}
x = in * f;
}
typedef long long LL;
const int SN = 300000 + 10;
const int inf = 0x7fffffff;
const int SM = 20000 + 10;
int fail[SN], ch[SN][26], que[SN], pre[SN], net[SN], is_end[SN];
LL max[SN << 2], C[SM], f[SM];
int head[SN], num, n, m, CLOCK, TIME, TEST, len;
char s[SN];
vector<char> A[SM];
struct Edge {
int v, next;
}E[SN];
void Add_E(int u, int v) {
E[++num].v = v, E[num].next = head[u], head[u] = num;
}
void Insert(int len, int num) {
int now = 0, to;
for(int i = 1; i <= len; i++) {
to = s[i] - 'a';
if(!ch[now][to])
ch[now][to] = ++TIME;
now = ch[now][to];
}
is_end[num] = now;
}
void Getfail() {
int front = 1, tail = 0;
for(int i = 0; i < 26; i++)
if(ch[0][i])
Add_E(0, ch[0][i]), fail[ch[0][i]] = 0, que[++tail] = ch[0][i];
while(front <= tail) {
int now = que[front++], u;
for(int i = 0; i < 26; i++) {
u = ch[now][i];
if(!u) {ch[now][i] = ch[fail[now]][i]; continue ;}
que[++tail] = u;
int v = fail[now];
while (v && !ch[v][i]) v = fail[v];
fail[u] = ch[v][i], Add_E(fail[u], u);
}
}
}
void Dfs(int u, int fa) {
pre[u] = ++CLOCK;
for(int i = head[u]; i; i = E[i].next) {
int to = E[i].v;
if(to == fa) continue ;
Dfs(to, u);
}
net[u] = CLOCK;
}
void build(int l, int r, int rt) {
max[rt] = -inf;
if(l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, rt << 1), build(mid + 1, r, rt << 1 | 1);
}
void Modify(int pos, LL C, int l, int r, int rt) {
if(l == r) {
max[rt] = Max(max[rt], C);
return ;
}
int mid = (l + r) >> 1;
if(pos <= mid) Modify(pos, C, l, mid, rt << 1);
else Modify(pos, C, mid + 1, r, rt << 1 | 1);
max[rt] = Max(max[rt << 1], max[rt << 1 | 1]);
}
LL Query(int QL, int QR, int l, int r, int rt) {
if(QL <= l && QR >= r) return max[rt];
int mid = (l + r) >> 1;
if(QL <= mid && QR > mid)
return Max(Query(QL, QR, l, mid, rt << 1),
Query(QL, QR, mid + 1, r, rt << 1 | 1));
if(QL <= mid) return Query(QL, QR, l, mid, rt << 1);
else return Query(QL, QR, mid + 1, r, rt << 1 | 1);
}
int main() {
Read(TEST);
while(TEST--) {
memset(f, -0x7f, sizeof f);
memset(head, 0, sizeof head);
CLOCK = TIME = num = 0;
Read(n);
for(int i = 1; i <= n; i++) {
scanf("%s", s + 1);
len = strlen(s + 1), Insert(len, i);
Read(C[i]);
A[i].clear();
for(int j = 0; j < len; j++)
A[i].push_back(s[j + 1]);
}
Getfail();
Dfs(0, -1);
build(1, CLOCK, 1);
vector<char> :: iterator it;
LL ans = 0;
for(int i = n; i >= 1; i--) {
f[i] = Max(f[i], C[i]);
LL g = Query(pre[is_end[i]], net[is_end[i]], 1, CLOCK, 1);
f[i] = Max(f[i], g + C[i]);
ans = Max(ans, f[i]);
int now = 0;
for(it = A[i].begin(); it != A[i].end(); it++) {
now = ch[now][(*it) - 'a'];
Modify(pre[now], f[i], 1, CLOCK, 1);
}
}
for(int i = 0; i <= TIME; i++)
memset(ch[i], 0, sizeof ch[i]), fail[i] = 0;
for(int i = 1; i <= n; i++) is_end[i] = 0;
printf("%lld\n", ans);
}
return 0;
}