BZOJ2905题解

网上的题解太粗糙了,我写篇详细的。

题目链接:BZOJ2905

题意应该很清晰。

多个单词(即模式串),想到AC自动机。

我们考虑在AC自动机当中一个串是另一个串的子串是什么情况。

设两个字符串为ST,如果TS的子串,那么由fail树的性质,S必有一个前缀节点S,在fail树上是T的子节点。

那么我们可以建出fail树,再依照1n的顺序遍历每一个串。那么可以预处理出原来trie树中每个节点的祖先,从下到上依次遍历这个串的每个节点,记录这个路径上没有计算本串贡献的最大答案maxx,那这个maxx再加上当前串的权值ans就是选了这个串时的最大W值了,那么显然,求出的ans会对所有串尾节点fail树中的子节点受到影响,将这个值更新当前串尾节点的所有子节点上

那么理清思路,我们目前需要做的事情是:

  1. fail树上查询链区间的最大值。

  2. 对一个点子树上的所有点增加一个权值。

要在树上支持这两种操作,答案应该很简单了。

Ctrl+A获取正确答案。

dfs+线段树\ \ \ 或\ \ \ 树链剖分

树剖懒得打了,就放一下dfn+线段树的代码吧。

其实树剖就是拿dfn+线段树维护的。。

在维护时要注意一些小细节,代码注释会体现。

// BZOJ2905
#include <bits/stdc++.h>
#define __(i) memset(i, 0, sizeof i)
#define N 300005
using namespace std;
int TEST;
int trie[N][27], tot;
int las[N], fa[N];
struct node {
    int to, nxt;
} e[N << 1];
int head[N], bnt;
void add(int u, int v) {
    e[++bnt].to = v;
    e[bnt].nxt = head[u];
    head[u] = bnt;
}
int in[N], out[N], tim;
void dfs(int x) {
    if (x)//注意!0是树根,不能算在dfn维护的区间之内!
        in[x] = ++tim;
    for (int i = head[x]; i; i = e[i].nxt)
        dfs(e[i].to);
    out[x] = tim;
}
void insert(char *s, int num) {
    int len = strlen(s), p = 0;
    for (int i = 0; i < len; i++) {
        int ch = s[i] - 'a';
        if (!trie[p][ch]) {
            trie[p][ch] = ++tot;
            fa[tot] = p;
        }
        p = trie[p][ch];
    }
    las[num] = p;//标记每个串的结束节点
}
int fail[N];
queue<int>q;
void build() {
    for (int i = 0; i < 26; i++)
        if (trie[0][i])
            q.push(trie[0][i]);
    while (!q.empty()) {
        int p = q.front();
        q.pop();
        for (int i = 0; i < 26; i++) {
            if (trie[p][i])
                fail[trie[p][i]] = trie[fail[p]][i], q.emplace(trie[p][i]);
            else
                trie[p][i] = trie[fail[p]][i];
        }
    }
}
struct Node {
    int maxx, flg;
} t[N << 2];
#define maxx(i) t[i].maxx
#define flg(i) t[i].flg
#define lc (p << 1)
#define rc (p << 1 | 1)
void push_up(int p) {
    maxx(p) = max(maxx(lc), maxx(rc));
}
void push_down(int p) {
    if (flg(p) == 0)
        return;
    maxx(lc) = max(maxx(lc), flg(p));
    maxx(rc) = max(maxx(rc), flg(p));
    flg(lc) = max(flg(p), flg(lc));
    flg(rc) = max(flg(p), flg(rc));
    flg(p) = 0;
}
// 区间修改,单点查询
void change(int p, int l, int r, int vl, int vr, int val) {
    if (vl <= l && vr >= r) {
        maxx(p) = max(maxx(p), val);
        flg(p) = max(flg(p), val);
        return;
    }
    push_down(p);
    int mid = (l + r) >> 1;
    if (vl <= mid)
        change(lc, l, mid, vl, vr, val);
    if (vr > mid)
        change(rc, mid + 1, r, vl, vr, val);
    push_up(p);
}
int query (int p, int l, int r, int x) {
    if (l == r)
        return maxx(p);
    push_down(p);
    int mid = (l + r) >> 1;
    if (x <= mid)
        return query(lc, l, mid, x);
    else
        return query(rc, mid + 1, r, x);
}
char s[N];
int val[N];
// 多测要清空!!
void ___() {
    tot = tim = bnt = 0;
    __(las);
    __(fa);
    __(trie);
    __(e);
    __(t);
    __(head);
    __(in);
    __(out);
    __(val);
    __(fail);
}
int main() {
    scanf("%d", &TEST);
    while (TEST--) {
        ___();
        int n, res = 0;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%s%d", s, &val[i]);
            insert(s, i);
        }
        build();
        for (int i = 1; i <= tot; i++)
            add(fail[i], i);//建出fail树
        dfs(0);//构建dfn
        for (int i = 1; i <= n; i++) {
            int p = las[i], ans = 0;
            while (p) {//从每个串的底部暴力跳父亲
                ans = max(ans, query(1, 1, tot, in[p]));
                p = fa[p];
            }
            p = las[i];//记得归位
            ans = max(ans + val[i], ans);
            ans = max(ans, 0);
            //在fail树以下的所有点都可以被这条串"荫蔽",答案需要更新
            change(1, 1, tot, in[p], out[p], ans);
        }
        cout << res << "\n";
    }
    return 0;
}
posted @   长安19路  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示