Description

对于一个字符串|S|,我们定义fail[i],表示最大的x使得S[1..x]=S[i-x+1..i],满足(x<i)

显然对于一个字符串,如果我们将每个0<=i<=|S|看成一个结点,除了i=0以外i向fail[i]连边,这是一颗树的形状,根是0

我们定义这棵树是G(S),设f(S)是G(S)中除了0号点以外所有点的深度之和,其中0号点的深度为-1

定义key(S)等于S的所有非空子串S'的f(S')之和

给定一个字符串S,现在你要实现以下几种操作:

1.在S最后面加一个字符

2.询问key(S)

善良的出题人不希望你的答案比long long大,所以你需要将答案对1e9+7取模

Input

第一行一个正整数Q

Q<=10^5

第二行一个长度为Q的字符串S

Output

输出Q行,第i行表示前i个字符组成的字符串的答案

Input示例

5

abaab

Output示例

0

0

1

4

9


思路

这题真的好,我用了一个晚上+一个下午才想明白+做出来

然后说思路

首先发现一下性质

考虑分解一个串S的\(f(S)\)的贡献

为了解决这个问题我们考虑分解每一个前缀的贡献

那么前缀子串S的贡献怎么统计?

  • 性质1:对于任意一个前缀,它的贡献是除了本身这个子串在S中出现的次数

    • 证明:

      对于一个前缀位置x和一个匹配位置p,

      在\(G(S)\)上x一定是p的祖先,因此带来出现次数的贡献

所以就可以把\(f(S)\)分解成每个前缀的出现次数了

那么\(key(S)=\sum_{S'是S的非空子串}f(S')\)怎么计算呢?

这所有的子串可以拆分成若干多个前缀

所以考虑计算每个子串对\(key(s)\)的贡献,设这个子串的结束位置是\(endpos(S')\),那么就会对\(len(S)-endpos(S')+1\)个非空子串产生贡献

这个贡献并不好统计

又因为这道题需要计算每一次的增量,反而让我们的计算变得简单了

因为新增的串只是后缀

  • 性质2:每一次答案增量是上一次的kay(len-1)+新增的后缀在原串中的出现次数

    • 证明:

      上一次的key(len-1)很好理解啊

      因为对于之前的每个子串,产生贡献的子串又多了一个

      也就是变成了相对于原来的\(len(S)+1-endpos(S')+1\),所以加上\(key(len-1)\)

      然后为什么又新增的后缀在原串中的出现次数呢?

      是因为每次要考虑如果对于一个子串在后面多出现了一次,贡献就需要++

      所以新增的贡献就是除开新增后缀之外的所有后缀出现次数

然后考虑用LCT动态维护在parent树上的链接关系

同时维护right和答案

至于这个答案怎么维护

因为后缀自动机每个节点表示了一个或多个串,所以在统计的时候需要乘上\(t->maxl - t->prt->maxl\)的贡献,这样就可以统计所有贡献

每一次进行扩展的时候可以维护关系,然后扩展结束之后当前节点就表示最后一个点(可以接收整个字符串)

然后只需要把从这个节点到root的parent树提取出来,先计算答案然后累加贡献就好了


真的是好题

墙裂推荐


  1. //Author: dream_maker
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. //----------------------------------------------
  5. //typename
  6. typedef long long ll;
  7. //convenient for
  8. #define for_up(a, b, c) for (int a = b; a <= c; ++a)
  9. #define for_down(a, b, c) for (int a = b; a >= c; --a)
  10. #define for_vector(a, b) for (int a = 0; a < (signed)b.size(); ++a)
  11. //inf of different typename
  12. const int INF_of_int = 1e9;
  13. const ll INF_of_ll = 1e18;
  14. //fast read and write
  15. template <typename T>
  16. void Read(T &x){
  17. bool w = 1;x = 0;
  18. char c = getchar();
  19. while(!isdigit(c) && c != '-')c = getchar();
  20. if(c == '-')w = 0, c = getchar();
  21. while(isdigit(c)) {
  22. x = (x<<1) + (x<<3) + c -'0';
  23. c = getchar();
  24. }
  25. if(!w)x=-x;
  26. }
  27. template <typename T>
  28. void Write(T x){
  29. if(x < 0) {
  30. putchar('-');
  31. x=-x;
  32. }
  33. if(x > 9)Write(x / 10);
  34. putchar(x % 10 + '0');
  35. }
  36. //----------------------------------------------
  37. const int Mod = 1e9 + 7;
  38. const int N = 1e5 + 10;
  39. const int CHARSET_SIZE = 26;
  40. int add(int a, int b) {
  41. a += b;
  42. if(a >= Mod) a -= Mod;
  43. return a;
  44. }
  45. int mul(int a, int b) {
  46. return 1ll * a * b %Mod;
  47. }
  48. namespace LCT {
  49. struct Splay {
  50. Splay *ch[2], *fa;
  51. int val, len, sum_len, right;
  52. int tag; //add tag of siz of right
  53. //val euqal to sum (len * right)
  54. Splay():val(0), len(0), sum_len(0), right(0), tag(0){}
  55. }_null,*null=&_null;
  56. Splay *newnode() {
  57. Splay *t=new Splay();
  58. t->fa = t->ch[0] = t->ch[1] = null;
  59. return t;
  60. }
  61. bool isroot(Splay *t) {
  62. return t->fa->ch[0] != t && t->fa->ch[1] != t;
  63. }
  64. bool Son(Splay *t) {
  65. return t->fa->ch[1] == t;
  66. }
  67. void pushnow(Splay *t, int vl){
  68. t->tag = add(t->tag, vl);
  69. t->right = add(t->right, vl);
  70. t->val = add(t->val, mul(vl, t->sum_len));
  71. }
  72. void pushup(Splay *t) {
  73. t->sum_len = t->len;
  74. t->val = mul(t->sum_len, t->right);
  75. if (t->ch[0] != null) {
  76. t->sum_len = add(t->sum_len, t->ch[0]->sum_len);
  77. t->val = add(t->val, t->ch[0]->val);
  78. }
  79. if (t->ch[1] != null) {
  80. t->sum_len = add(t->sum_len, t->ch[1]->sum_len);
  81. t->val = add(t->val, t->ch[1]->val);
  82. }
  83. }
  84. void pushdown(Splay *t) {
  85. if(!isroot(t))pushdown(t->fa);
  86. if (!t->tag) return;
  87. if (t->ch[0] != null) pushnow(t->ch[0], t->tag);
  88. if (t->ch[1] != null) pushnow(t->ch[1], t->tag);
  89. t->tag = 0;
  90. }
  91. void rotate(Splay *t) {
  92. Splay *f = t->fa, *g = f->fa;
  93. bool a = Son(t),b = a ^ 1;
  94. if (!isroot(f)) g->ch[Son(f)] = t;
  95. t->fa = g;
  96. f->ch[a] = t->ch[b];
  97. t->ch[b]->fa = f;
  98. t->ch[b] = f;
  99. f->fa = t;
  100. pushup(f);
  101. pushup(t);
  102. }
  103. void splay(Splay *t) {
  104. pushdown(t);
  105. while (!isroot(t)) {
  106. Splay *f = t->fa;
  107. if (!isroot(f)) {
  108. if (Son(t)^Son(f)) rotate(t);
  109. else rotate(f);
  110. }
  111. rotate(t);
  112. }
  113. }
  114. void access(Splay *t) {
  115. Splay *tmp = null;
  116. while (t != null) {
  117. splay(t);
  118. t->ch[1] = tmp;
  119. pushup(t);
  120. tmp = t;t = t->fa;
  121. }
  122. }
  123. //makeroot function in sam doesn't need rev
  124. void makeroot(Splay *x) {
  125. access(x);
  126. splay(x);
  127. }
  128. void cut(Splay *x, Splay *y) {
  129. makeroot(x);
  130. splay(y);
  131. y->ch[1] = null;
  132. x->fa = null;
  133. pushup(y);
  134. }
  135. void link(Splay *x, Splay *y) {
  136. makeroot(y);
  137. x->fa = y;
  138. }
  139. };
  140. using namespace LCT;
  141. namespace Suffix_Automaton {
  142. struct Sam {
  143. Sam *ch[CHARSET_SIZE], *prt;
  144. int maxl, right;
  145. Splay *splay;
  146. Sam(int maxl = 0, int right = 0):ch(), prt(NULL), maxl(maxl), right(right) {
  147. splay = newnode();
  148. }
  149. }*root, *last;
  150. void init() {
  151. last = root = new Sam;
  152. }
  153. void modify(Sam *t) {
  154. t->splay->len = t->maxl - t->prt->maxl;
  155. t->splay->right = t->right;
  156. pushup(t->splay);
  157. }
  158. int extend(int c) {
  159. Sam *u = new Sam(last->maxl + 1, 1), *v = last;
  160. for (;v && !v->ch[c]; v = v->prt) v->ch[c] = u;
  161. if(!v){
  162. u->prt = root;
  163. modify(u);
  164. link(u->splay, root->splay);
  165. }else if(v->maxl + 1 == v->ch[c]->maxl){
  166. u->prt = v->ch[c];
  167. modify(u);
  168. link(u->splay, v->ch[c]->splay);
  169. }else{
  170. Sam *n = new Sam(v->maxl + 1, 0),*o = v->ch[c];
  171. copy(o->ch, o->ch + CHARSET_SIZE, n->ch);
  172. n->prt = o->prt;
  173. makeroot(o->splay);
  174. n->right = o->right = o->splay->right;
  175. modify(n);
  176. link(n->splay, o->prt->splay);
  177. cut(o->splay, o->prt->splay);
  178. o->prt = u->prt = n;
  179. modify(o);
  180. modify(u);
  181. link(o->splay, n->splay);
  182. link(u->splay, n->splay);
  183. for(;v && v->ch[c] == o; v = v->prt) v->ch[c] = n;
  184. }
  185. last = u;
  186. makeroot(u->splay);
  187. Splay *now = u->splay->ch[0];
  188. int res = now->val;
  189. pushnow(now, 1);
  190. return res;
  191. }
  192. };
  193. using namespace Suffix_Automaton;
  194. int f[N],n;
  195. char c[N];
  196. int main() {
  197. freopen("input.txt", "r", stdin);
  198. Read(n);
  199. scanf("%s",c+1);
  200. Suffix_Automaton::init();
  201. for_up(i, 1, n)
  202. f[i] = add(f[i-1], extend(c[i]-'a'));
  203. for_up(i, 1, n) {
  204. f[i] = add(f[i], f[i-1]);
  205. Write(f[i]);
  206. putchar('\n');
  207. }
  208. return 0;
  209. }

51nod 1600 Simple KMP【后缀自动机+LCT】【思维好题】*的更多相关文章

  1. 51Nod 1600 Simple KMP 解题报告

    51Nod 1600 Simple KMP 对于一个字符串\(|S|\),我们定义\(fail[i]\),表示最大的\(x\)使得\(S[1..x]=S[i-x+1..i]\),满足\((x<i ...

  2. 51Nod 1600 Simple KMP SAM+LCT/树链剖分

    1600 Simple KMP 对于一个字符串|S|,我们定义fail[i],表示最大的x使得S[1..x]=S[i-x+1..i],满足(x<i)显然对于一个字符串,如果我们将每个0<= ...

  3. 51nod 1600 Simple KMP

    又被机房神犇肉丝哥哥和glory踩爆了 首先这个答案的输出方式有点套路,当前的答案=上一个答案+每一个后缀的f值=上一个答案+上一次算的每个后缀的f值+当前每个后缀的深度 这个题意给了个根深度为-1有 ...

  4. bzoj2555(后缀自动机+LCT)

    题目描述 (1):在当前字符串的后面插入一个字符串 (2):询问字符串s在当前字符串中出现了几次?(作为连续子串) 你必须在线支持这些操作. 题解 做法很自然,建出后缀自动机,维护每个节点的right ...

  5. BZOJ2555 SubString 【后缀自动机 + LCT】

    题目 懒得写背景了,给你一个字符串init,要求你支持两个操作 (1):在当前字符串的后面插入一个字符串 (2):询问字符串s在当前字符串中出现了几次?(作为连续子串) 你必须在线支持这些操作. 输入 ...

  6. bzoj 2555 SubString —— 后缀自动机+LCT

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2555 建立后缀自动机,就可以直接加入新串了: 出现次数就是 Right 集合的大小,需要查询 ...

  7. bzoj 2555: SubString【后缀自动机+LCT】

    一直WA--找了半天错的发现居然是解密那里的mask其实是不能动的--传进去的会变,但是真实的那个不会变-- 然后就是后缀自动机,用LCT维护parent树了--注意不能makeroot,因为自动机的 ...

  8. 【BZOJ4545】DQS的trie 后缀自动机+LCT

    [BZOJ4545]DQS的trie Description DQS的自家阳台上种着一棵颗粒饱满.颜色纯正的trie. DQS的trie非常的奇特,它初始有n0个节点,n0-1条边,每条边上有一个字符 ...

  9. bzoj 2555: SubString 后缀自动机+LCT

    2555: SubString Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 688  Solved: 235[Submit][Status][Dis ...

随机推荐

  1. python正则表达式 Python Re模块

    最近在学python 练习的时候随手写的,方便以后自己参考~如果能对其他同学有所帮助就再好不过了 希望大家指正哦~  我会随时整理的,先这样~ 正则表达式 1.元字符([ ]),它用来指定一个char ...

  2. Android -- 提交数据到服务器,Get Post方式, 异步Http框架提交

    1. 发送请求到服务器有几种方式 (1)HttpURLConnection (2)Httpclient 同步框架 (3)AsyncHttpClient 异步框架 (https://github.com ...

  3. python中装饰器的执行细节

    本文代码借用 廖雪峰的python教程(官网:http://www.liaoxuefeng.com/) 不了解装饰器的可以先看教程 直接上带参数装饰器的代码 def log(text): def de ...

  4. ADC和RTC的寄存器的读取

    ADC的寄存器读取,int adc_read(void){ int result; #if ADSTART==0 result = ADC.ADCDAT0&0x3ff; while(!(ADC ...

  5. Mysql(基础篇)

    linux下的mysql操作 1.# 打开 MySQL 服务 sudo service mysql start 2.#使用 root 用户登录,密码为空 mysql -u root 3.创建数据库 C ...

  6. [转载]Java生成Word文档

    在开发文档系统或办公系统的过程中,有时候我们需要导出word文档.在网上发现了一个用PageOffice生成word文件的功能,就将这块拿出来和大家分享. 生成word文件与我们编辑word文档本质上 ...

  7. poj 1258 Agri-Net 最小生成树 prim算法+heap不完全优化 难度:0

    Agri-Net Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 41230   Accepted: 16810 Descri ...

  8. Marketing™Series用户手册(Marketing™Series Manual)

    起源(Origin) 每日构建(Daily Build) 软件不支持的功能(Functions which are not supported.) 软件支持的功能(Functions which ar ...

  9. nodejs之log4js日志记录模块简单配置使用

    在我的一个node express项目中,使用了log4js来生成日志并且保存到文件里,生成的文件如下: 文件名字叫:access.log 如果在配置log4js的时候允许了同时存在多个备份log文件 ...

  10. IIS:连接数、并发连接数、最大并发工作线程数、应用程序池的队列长度、应用程序池的最大工作进程数详解

    Internet Information Services(IIS,互联网信息服务),是由微软公司提供的基于运行Microsoft Windows的互联网基本服务.最初是Windows NT版本的可选 ...