[BZOJ2905] 背单词

本文介绍了一种解决最长子串序列问题的方法,通过构建fail树和使用线段树进行子树查询来实现DP算法,以求得权值最大的不连续子序列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给出长度为 nn 的字符串序列,第 i 个字符串有价值 cici

要求从中选出允许不连续的子序列,使得前一个是后一个的子串,同时权值和最大。

数据范围

1n2×104,1len3×1051≤n≤2×104,1≤∑len≤3×105

思路

考虑在 failfail 树上 DP ,在序列上从后往前面 DP 对于当前 ii 这个字符串若允许放在 j 前面,那么 iifail 树上的末尾节点的子树中一定有 jj 的节点,也就是子树查询 DP 最大值,这个可以用线段树维护

由于总长度不超过 3×105 所以我们在修改的时候可以暴力从 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值