G. 背单词

内存限制:256 MiB 时间限制:1000 ms 标准输入输出
题目类型:传统 评测方式:文本比较
 

题目描述

给定一张包含N个单词的表,每个单词有个价值W。要求从中选出一个子序列使得其 中的每个单词是后一个单词的子串,最大化子序列中W的和。

输入格式

第一行一个整数TEST,表示数据组数。 接下来TEST组数据,每组数据第一行为一个整数N。 接下来N行,每行为一个字符串和一个整数W。

输出格式

TEST行,每行一个整数,表示W的和的最大值。

数据规模 设字符串的总长度为Len 30.的数据满足,TEST≤5,N≤500,Len≤10^4 100.的数据满足,TEST≤10,N≤20000,Len≤3*10^5


析:又是一道好题,这道题将AC自动机与线段树,dp,以及 dfs 序结合了起来;

  首先我们要明确这样一个事情,S是T的字串,相当于 T  的一个前缀可以通过 Fail 树遍历到 S 的末尾结点,也就是说, S 的末尾节点是 T 的某个前缀在 Fail 树上的祖先;

  那么这道题思路就清晰了,首先可以写出 dp 方程 :f[i]=max(x)+w[i] ,表示 在前 i 个单词中,当前枚举到第 i 个单词且选择它的最大值, max(x) 表示当前单词前缀的最大值;

  那么此时我们的问题就在于 1.如何求得前缀? 2.如何求得区间(单点)最大值?

  对于第一个问题,我们可以使用 fa 数组进行回溯查询:

  1. if(!use[now].son[p])
  2. {
  3. use[now].son[p]=++num;
  4. fa[use[now].son[p]]=now;
  5. }

  这是在构建字典树的过程中记录每一个字符的父亲节点,我们在计算过程中就可以:

  1. while(p)
  2. {
  3. f[i]=max(f[i],query(rt,1,num,l[p]));
  4. p=fa[p];
  5. }

  那么考虑第二个问题,区间查询,我们同时还要考虑到,每次在我们枚举一次选择的单词后,我们都要判断选或不选哪个是最优解,然后对一段区间进行更新,所以说,我们不仅需要区间查询,还需要区间修改的操作,

   看这数据范围,显然我们可以想到线段树。那么我们在查询,修改的过程中如何确定区间呢? 这里利用 dfs 序就是一种很妙的思路,我们求出 in[],与out[] 就可以知道当前单词的控制区间。

  那么问题来了,我们要构建一颗什么样的 dfs 树呢?

  显然,若考虑当前单词 i ,fail[i] ,fa[i],fail[fa[i]] ,那么很明显,fail[fa[i]] 应该是控制区间最大的那一个,所以,我们就要从每个节点的 fail 指针向当前单词 连一条边,进行 dfs ;

  这里我们再解释一下为什么是单点查询,注意题目要求:

>  从中选出一个子序列使得其中的每个单词是后一个单词的子串

  1.假设现在有个序列 ABCD ,那么假设我的单词分别为 AB , 和 CD,那么如果我两个同时拿的话就无法满足题义

  2.若每次我们都之考虑某一个前缀的最大值,那么递推过来的一定是满足条件的最大值!!

  那么,在这颗线段树中,我们要维护的就是每个单词选或不选的最大值,所以在区间更新的时候我们都要取 max,到这里应该解释的差不多了;

 代码:

  1. #include<bits/stdc++.h>
  2. #define re register int
  3. using namespace std;
  4. const int N=3e6+10;
  5. int T,n,cnt,tot,rt,timi,num=1;
  6. char s[N];
  7. int w[N],ed[N],l[N],r[N],fa[N];
  8. int head[N],to[N<<1],next[N<<1];
  9. long long f[N];
  10. long long maxx;
  11. bool vis[N];
  12. queue<int> q;
  13. struct CUN
  14. {
  15. int flag,fail;
  16. int son[30];
  17. void clean()
  18. {
  19. flag=0;
  20. fail=0;
  21. memset(son,0,sizeof(son));
  22. }
  23. }use[N];
  24. struct C2
  25. {
  26. int lc,rc,sum,lazy;
  27. void clean()
  28. {
  29. lc=0;
  30. rc=0;
  31. sum=0;
  32. lazy=0;
  33. }
  34. }t[N];
  35. void in()
  36. {
  37. for(re i=0;i<=max(cnt,tot);i++)
  38. use[i].clean();
  39. for(re i=0;i<=max(cnt,tot);i++)
  40. t[i].clean();
  41. cnt=0;
  42. num=1;
  43. maxx=0;
  44. tot=0;
  45. timi=0;
  46. memset(l,0,sizeof(l));
  47. memset(r,0,sizeof(r));
  48. memset(vis,0,sizeof(vis));
  49. memset(f,0,sizeof(f));
  50. memset(w,0,sizeof(w));
  51. memset(ed,0,sizeof(ed));
  52. memset(head,0,sizeof(head));
  53. memset(to,0,sizeof(to));
  54. memset(next,0,sizeof(next));
  55. memset(fa,0,sizeof(fa));
  56. while(!q.empty())
  57. q.pop();
  58. }
  59. void insert(char ss[],int pos)
  60. {
  61. int now=1;
  62. int l=strlen(ss);
  63. for(re i=0;i<l;i++)
  64. {
  65. int p=ss[i]-'a';
  66. if(!use[now].son[p])
  67. {
  68. use[now].son[p]=++num;
  69. fa[use[now].son[p]]=now;
  70. }
  71. now=use[now].son[p];
  72. }
  73. ed[pos]=now;
  74. }
  75. void Add(int x,int y)
  76. {
  77. to[++tot]=y;
  78. next[tot]=head[x];
  79. head[x]=tot;
  80. }
  81. void dfs(int x)
  82. {
  83. if(x)
  84. l[x]=++timi;
  85. for(re i=head[x];i;i=next[i])
  86. dfs(to[i]);
  87. r[x]=timi;
  88. }
  89. void build(int &p,int L,int R)
  90. {
  91. p=++cnt;
  92. if(L==R)
  93. return;
  94. int mid=(L+R)>>1;
  95. build(t[p].lc,L,mid);
  96. build(t[p].rc,mid+1,R);
  97. }
  98. void get_fail()
  99. {
  100. for(re i=0;i<26;i++)
  101. use[0].son[i]=1;
  102. use[1].fail=0;
  103. q.push(1);
  104. while(!q.empty())
  105. {
  106. int u=q.front();
  107. q.pop();
  108. int Fail=use[u].fail;
  109. for(re i=0;i<26;i++)
  110. {
  111. int v=use[u].son[i];
  112. if(!v)
  113. {
  114. use[u].son[i]=use[Fail].son[i];
  115. continue;
  116. }
  117. use[v].fail=use[Fail].son[i];
  118. q.push(v);
  119. }
  120. }
  121. for(re i=1;i<=num;i++)
  122. Add(use[i].fail,i);
  123. dfs(0);
  124. build(rt,1,num);
  125. }
  126. void pd(int p)
  127. {
  128. if(t[p].lazy==0)
  129. return;
  130. t[t[p].lc].lazy=max(t[t[p].lc].lazy,t[p].lazy);
  131. t[t[p].rc].lazy=max(t[t[p].rc].lazy,t[p].lazy);
  132. t[t[p].lc].sum=max(t[t[p].lc].sum,t[p].lazy);
  133. t[t[p].rc].sum=max(t[t[p].rc].sum,t[p].lazy);
  134. t[p].lazy=0;
  135. }
  136. void pp(int rt)
  137. {
  138. t[rt].sum=max(t[t[rt].lc].sum,t[t[rt].rc].sum);
  139. }
  140. long long query(int rt,int L,int R,int p)
  141. {
  142. if(L==R)
  143. return t[rt].sum;
  144. int mid=(L+R)>>1;
  145. pd(rt);
  146. if(p<=mid)
  147. return query(t[rt].lc,L,mid,p);
  148. return query(t[rt].rc,mid+1,R,p);
  149. }
  150. void updata(int p,int L,int R,int l,int r,int z)
  151. {
  152. if(l<=L&&R<=r)
  153. {
  154. t[p].sum=max(t[p].sum,z);
  155. t[p].lazy=max(t[p].lazy,z);
  156. return;
  157. }
  158. pd(p);
  159. int mid=(L+R)>>1;
  160. if(mid>=l)
  161. updata(t[p].lc,L,mid,l,r,z);
  162. if(mid<r)
  163. updata(t[p].rc,mid+1,R,l,r,z);
  164. pp(p);
  165. }
  166. void dp()
  167. {
  168. //f[i]=max{x}+w[i];
  169. for(re i=1;i<=n;i++)
  170. {
  171. int p=ed[i];
  172. while(p)
  173. {
  174. f[i]=max(f[i],query(rt,1,num,l[p]));
  175. p=fa[p];
  176. }
  177. f[i]=max(0*1ll,f[i]+w[i]);
  178. updata(rt,1,num,l[ed[i]],r[ed[i]],f[i]);
  179. }
  180. for(re i=1;i<=n;i++)
  181. maxx=max(maxx,f[i]);
  182. printf("%lld\n",maxx);
  183. }
  184. signed main()
  185. {
  186. scanf("%d",&T);
  187. while(T--)
  188. {
  189. scanf("%d",&n);
  190. in();
  191. for(re i=1;i<=n;i++)
  192. {
  193. scanf("%s",s);
  194. scanf("%d",&w[i]);
  195. insert(s,i);
  196. }
  197. get_fail();
  198. dp();
  199. }
  200. }

背单词(AC自动机+线段树+dp+dfs序)的更多相关文章

  1. BZOJ 2905: 背单词 AC自动机+fail树+dfs序+线段树

    Description 给定一张包含N个单词的表,每个单词有个价值W.要求从中选出一个子序列使得其中的每个单词是后一个单词的子串,最大化子序列中W的和. Input 第一行一个整数TEST,表示数据组 ...

  2. BZOJ2905: 背单词 AC自动机+fail树+线段树

    $zjq$神犇一眼看出$AC$自动机 $Orz$ 直接就讲做法了 首先对每个串建出$AC$自动机 将$fail$树找到 然后求出$dfs$序 我们发现一个单词 $S_i$是$S_j$的子串当且仅当$S ...

  3. hdu 4117 GRE Words (ac自动机 线段树 dp)

    参考:http://blog.csdn.net/no__stop/article/details/12287843 此题利用了ac自动机fail树的性质,fail指针建立为树,表示父节点是孩子节点的后 ...

  4. hdu 4117 -- GRE Words (AC自动机+线段树)

    题目链接 problem Recently George is preparing for the Graduate Record Examinations (GRE for short). Obvi ...

  5. CF877E Danil and a Part-time Job 线段树维护dfs序

    \(\color{#0066ff}{题目描述}\) 有一棵 n 个点的树,根结点为 1 号点,每个点的权值都是 1 或 0 共有 m 次操作,操作分为两种 get 询问一个点 x 的子树里有多少个 1 ...

  6. HDU4117 GRE WORDS(AC自动机+线段树维护fail树的dfs序)

    Recently George is preparing for the Graduate Record Examinations (GRE for short). Obviously the mos ...

  7. BZOJ2434:[NOI2011]阿狸的打字机(AC自动机,线段树)

    Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的 ...

  8. HDU 5069 Harry And Biological Teacher(AC自动机+线段树)

    题意 给定 \(n\) 个字符串,\(m\) 个询问,每次询问 \(a\) 字符串的后缀和 \(b\) 字符串的前缀最多能匹配多长. \(1\leq n,m \leq 10^5\) 思路 多串匹配,考 ...

  9. BZOJ 3172: [Tjoi2013]单词 [AC自动机 Fail树]

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 3198  Solved: 1532[Submit][Status ...

随机推荐

  1. 手写Spring,定义标记类型Aware接口,实现感知容器对象

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 同事写的代码,我竟丝毫看不懂! 大佬的代码,就像 "赖蛤蟆泡青蛙,张的丑玩 ...

  2. windows 安装Git详解

    windows 安装Git详解 一.Git简介 Git是一个开源的分布式版本控制系统,可以有效.高速的处理从很小到非常大的项目版本管理. Git 是 Linus Torvalds 为了帮助管理 Lin ...

  3. 《手把手教你》系列基础篇(五)-java+ selenium自动化测试- 创建首个自动化脚本(详细教程)

    1.简介 前面几篇宏哥介绍了两种(java和maven)环境搭建和三大浏览器的启动方法,这篇文章宏哥将要介绍第一个自动化测试脚本.前边环境都搭建成功了,浏览器也驱动成功了,那么我们不着急学习其他内容, ...

  4. Gym 101334E dp

    分析: 这一题给出的遍历的点的序列,不是树的中序遍历,前序遍历,只要遇到一个节点就打印一个节点.关键点就在,这个序列的首字母和尾字母一定要相同,因为最终都会回到根节点,那么每一个子树也一样. 状态: ...

  5. 【转】JAVA四种引用(强引用,弱引用,软引用,虚引用)

    转自:http://www.cnblogs.com/gudi/p/6403953.html 1.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器 ...

  6. SpringBoot:SpringCloud与SpringBoot兼容版本参(其它组件兼容情况)

    SpringCloud --- Springboot 版本兼容 SpringCloud SpringBoot Edgware.SR5 >=1.5.0.RELEASE and <=1.5.2 ...

  7. php-高级计算器

    HTML代码: <!doctype html><html lang="en"><head> <meta charset="UTF ...

  8. docker起不来报错:Failed to start Docker Application Container Engine.

    报错信息如下: [root@localhost localdisk]# systemctl restart docker Job for docker.service failed because t ...

  9. cordova自定义插件开发流程

    cordova自定义插件开发:1.cordova安装:npm install -g cordova2.plugman安装:npm install -g plugman3.cordova创建工程:cor ...

  10. XML技术

    XML是一种可扩展标记语言,用来标记数据.定义数据类型,1998年由W3W发布1.0.版本,与HTML语言相比,可以自定义可扩展标签格式,但是语法严格. XML可以用来存储数据,可移植性强,主要充当配 ...