背单词(非题解,仅代码,不过代码有注释)
背单词(非题解,仅代码,不过代码有注释)
#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;
}
与你的日常,便是奇迹

浙公网安备 33010602011771号