A
B

背单词(非题解,仅代码,不过代码有注释)

背单词(非题解,仅代码,不过代码有注释

#include<bits/stdc++.h>
#define int long long
#define Blue_Archive return 0
#define Girls_Band_Cry return
#define op DZC
#define itn int
#define ull unsigned long long
using namespace std;
const int N = 3e5 + 8;
const int M = 1e4;
const int INF = 0x7f7f7f7f7f7f7f7f;
const int mod = 10007;
const int P = 133233;

int T;//测试数据数
int n;//子串数
int rt;
int tt;//数组模拟队列
int hh;//数组模拟队列
int cnt;//tire图用
int tot;//tire树用
int ans;//维护答案
int cur;
int l[N];
int r[N];
int w[N];
int q[N];
int f[N];
int h[N];//链式前向星
int fa[N];
int to[N * 2];//链式前向星
int rec[N];
int net[N * 2];//链式前向星
int fail[N];//tire树
int dfstime;//求dfs序用
int nxt[N][26];//tire树
string ch;

struct miku
{
    int ls,rs;
    int mx,tag;//tag相当于lazy,mx维护区间最值
}sgt[N * 2];//线段树

inline int max(int a,int b)//重载最大值
{
    if(a > b) return a;
    return b;
}

inline void clear()//初始化
{
    for(int i = 1;i <= n;i ++) f[i] = 0;
    for(int i = 0;i <= tot;i ++) fail[i] = l[i] = r[i] = h[i] = fa[i] = rec[i] = 0;
    for(int i = 1;i <= tot;i ++) net[i] = to[i] = 0;
    for(int i = 0;i <= tot;i ++) memset(nxt[i],0,sizeof(nxt[i]));
    for(int i = 1;i <= cnt;i ++) sgt[i].tag = sgt[i].mx = 0 , sgt[i].ls = sgt[i].rs = 0;
    tot = cnt = dfstime = 0;
    ans = 0;
}

inline void adde(int x,int y)//加边
{
    to[++ cnt] = y;
    net[cnt] = h[x];
    h[x] = cnt;
}

inline void insert(int x)//单个字母插入(实际上就是模板的插入,详见主函数)
{
    if(!nxt[cur][x])
    {
        nxt[cur][x] = ++ tot;
        fa[tot] = cur;
    } 
    cur = nxt[cur][x];//cur 相当于板子中的 now
}

inline void dfs(int x)//求dfs序
{
    if(x) l[x] = ++ dfstime;
    for(int i = h[x];i;i = net[i]) dfs(to[i]);
    r[x] = dfstime;
}

inline void build(int &x,int l,int r)
{
    x = ++ cnt;
    if(l == r) Girls_Band_Cry;
    int mid = (l + r) >> 1;
    build(sgt[x].ls,l,mid);
    build(sgt[x].rs,mid + 1,r);
}

inline void build_AC()
{
    tt = hh = 0;//数组模拟队列
    for(int i = 0;i < 26;i ++)
    {
        if(nxt[0][i])
        {
            q[tt ++] = nxt[0][i];//初始化队列
        } 
    }
    while(hh < tt)
    {
        int x = q[hh ++];//q.front
        for (int i = 0;i < 26;i ++)
        {
            if(nxt[x][i])
            {
                int p = fail[x];//子串的前缀
                while(!nxt[p][i] && p) p = fail[p];
                fail[nxt[x][i]] = nxt[p][i]; //不是p
                q[tt ++] = nxt[x][i];
            }
        }
    }
    for(int i = 1;i <= tot;i ++) adde(fail[i],i);//建边连图
    /*
        关于为什么要将 i 与 fail[i] 连边,一个原因是将tire树变成tire图,另一个更容易理解的原因是:我们要同时维护 i,fail[i],fa[i],fail[fa[i]] 等值,选用 i 与 fail[i] 建图连边所求的dfs序更容易在线段树内维护
                                        */
    dfs(0);//求dfs序
    cnt = 0;
    build(rt,1,tot);//建树
}

inline void pushup(int x)
{
    sgt[x].mx = max(sgt[sgt[x].ls].mx,sgt[sgt[x].rs].mx);//维护区间最值
}

inline void add(int x,int d)
{
    sgt[x].tag = max(sgt[x].tag,d);
    sgt[x].mx = max(sgt[x].mx,d);
}

inline void pushdown(int x)
{
    if(sgt[x].tag != 0)
    {
        add(sgt[x].ls,sgt[x].tag);
        add(sgt[x].rs,sgt[x].tag);
        sgt[x].tag = 0;
    }
}

inline void modify(int x,int l,int r,int ls,int rs,int d)//区间修改
{
    if(ls <= l && rs >= r)
    {
        add(x,d);
        Girls_Band_Cry;
    }
    pushdown(x);
    int mid = (l + r) >> 1;
    if(ls <= mid) modify(sgt[x].ls,l,mid,ls,rs,d);
    if(rs > mid) modify(sgt[x].rs,mid + 1,r,ls,rs,d);
    pushup(x);
}

inline int query(int x,int l,int r,int id)//单点查询
{
    if(l == r) return sgt[x].mx;
    pushdown(x);
    int mid = (l + r) >> 1;
    if(id <= mid) return query(sgt[x].ls,l,mid,id);
    return query(sgt[x].rs,mid + 1,r,id);
}

inline void Dodp()//DP : dp[i] = max(x) + w[i]
{
    for(int i = 1;i <= n;i ++)
    {
        int p = rec[i];
        while(p)//一直向前跳,取尽量多的前缀,即取尽量多的子串
        {
            f[i] = max(f[i],query(rt,1,tot,l[p]));//求max(x) : 表示当前单词前缀的最大值;
            p = fa[p];//向前回溯
        }
        f[i] += w[i];//加上w[i]
        f[i] = max(0ll,f[i]);//存在负值,判断是否取
        modify(rt,1,tot,l[rec[i]],r[rec[i]],f[i]);//修改区间,维护最值
    }
    for(int i = 1;i <= n;i ++) ans = max(ans,f[i]);//统计答案
}

signed main()
{
    // freopen("1.in","r",stdin);
    // freopen("T.out","w",stdout);
    cin >> T;
    while(T --)
    {
        clear();//多测不清空,根本不是人
        cin >> n;
        for(int i = 1;i <= n ;i ++)
        {
            cin >> ch >> w[i];
            int l = ch.length();
            cur = 0;
            for(int j = 0;j < l;j  ++)
            {
                insert(ch[j] - 'a');
            }
            rec[i] = cur;//将每个子串记录在tire树的最后一个字母上
        }
        build_AC();
        Dodp();
        cout << ans << "\n";
    }
    Blue_Archive;
}
posted @ 2025-07-31 07:02  MyShiroko  阅读(11)  评论(0)    收藏  举报