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树提取出来,先计算答案然后累加贡献就好了


真的是好题

墙裂推荐


//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define for_up(a, b, c) for (int a = b; a <= c; ++a)
#define for_down(a, b, c) for (int a = b; a >= c; --a)
#define for_vector(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x){
bool w = 1;x = 0;
char c = getchar();
while(!isdigit(c) && c != '-')c = getchar();
if(c == '-')w = 0, c = getchar();
while(isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if(!w)x=-x;
}
template <typename T>
void Write(T x){
if(x < 0) {
putchar('-');
x=-x;
}
if(x > 9)Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int Mod = 1e9 + 7;
const int N = 1e5 + 10;
const int CHARSET_SIZE = 26;
int add(int a, int b) {
a += b;
if(a >= Mod) a -= Mod;
return a;
}
int mul(int a, int b) {
return 1ll * a * b %Mod;
}
namespace LCT {
struct Splay {
Splay *ch[2], *fa;
int val, len, sum_len, right;
int tag; //add tag of siz of right
//val euqal to sum (len * right)
Splay():val(0), len(0), sum_len(0), right(0), tag(0){}
}_null,*null=&_null;
Splay *newnode() {
Splay *t=new Splay();
t->fa = t->ch[0] = t->ch[1] = null;
return t;
}
bool isroot(Splay *t) {
return t->fa->ch[0] != t && t->fa->ch[1] != t;
}
bool Son(Splay *t) {
return t->fa->ch[1] == t;
}
void pushnow(Splay *t, int vl){
t->tag = add(t->tag, vl);
t->right = add(t->right, vl);
t->val = add(t->val, mul(vl, t->sum_len));
}
void pushup(Splay *t) {
t->sum_len = t->len;
t->val = mul(t->sum_len, t->right);
if (t->ch[0] != null) {
t->sum_len = add(t->sum_len, t->ch[0]->sum_len);
t->val = add(t->val, t->ch[0]->val);
}
if (t->ch[1] != null) {
t->sum_len = add(t->sum_len, t->ch[1]->sum_len);
t->val = add(t->val, t->ch[1]->val);
}
}
void pushdown(Splay *t) {
if(!isroot(t))pushdown(t->fa);
if (!t->tag) return;
if (t->ch[0] != null) pushnow(t->ch[0], t->tag);
if (t->ch[1] != null) pushnow(t->ch[1], t->tag);
t->tag = 0;
}
void rotate(Splay *t) {
Splay *f = t->fa, *g = f->fa;
bool a = Son(t),b = a ^ 1;
if (!isroot(f)) g->ch[Son(f)] = t;
t->fa = g;
f->ch[a] = t->ch[b];
t->ch[b]->fa = f;
t->ch[b] = f;
f->fa = t;
pushup(f);
pushup(t);
}
void splay(Splay *t) {
pushdown(t);
while (!isroot(t)) {
Splay *f = t->fa;
if (!isroot(f)) {
if (Son(t)^Son(f)) rotate(t);
else rotate(f);
}
rotate(t);
}
}
void access(Splay *t) {
Splay *tmp = null;
while (t != null) {
splay(t);
t->ch[1] = tmp;
pushup(t);
tmp = t;t = t->fa;
}
}
//makeroot function in sam doesn't need rev
void makeroot(Splay *x) {
access(x);
splay(x);
}
void cut(Splay *x, Splay *y) {
makeroot(x);
splay(y);
y->ch[1] = null;
x->fa = null;
pushup(y);
}
void link(Splay *x, Splay *y) {
makeroot(y);
x->fa = y;
}
};
using namespace LCT;
namespace Suffix_Automaton {
struct Sam {
Sam *ch[CHARSET_SIZE], *prt;
int maxl, right;
Splay *splay;
Sam(int maxl = 0, int right = 0):ch(), prt(NULL), maxl(maxl), right(right) {
splay = newnode();
}
}*root, *last;
void init() {
last = root = new Sam;
}
void modify(Sam *t) {
t->splay->len = t->maxl - t->prt->maxl;
t->splay->right = t->right;
pushup(t->splay);
}
int extend(int c) {
Sam *u = new Sam(last->maxl + 1, 1), *v = last;
for (;v && !v->ch[c]; v = v->prt) v->ch[c] = u;
if(!v){
u->prt = root;
modify(u);
link(u->splay, root->splay);
}else if(v->maxl + 1 == v->ch[c]->maxl){
u->prt = v->ch[c];
modify(u);
link(u->splay, v->ch[c]->splay);
}else{
Sam *n = new Sam(v->maxl + 1, 0),*o = v->ch[c];
copy(o->ch, o->ch + CHARSET_SIZE, n->ch);
n->prt = o->prt;
makeroot(o->splay);
n->right = o->right = o->splay->right;
modify(n);
link(n->splay, o->prt->splay);
cut(o->splay, o->prt->splay);
o->prt = u->prt = n;
modify(o);
modify(u);
link(o->splay, n->splay);
link(u->splay, n->splay);
for(;v && v->ch[c] == o; v = v->prt) v->ch[c] = n;
}
last = u;
makeroot(u->splay);
Splay *now = u->splay->ch[0];
int res = now->val;
pushnow(now, 1);
return res;
}
};
using namespace Suffix_Automaton;
int f[N],n;
char c[N];
int main() {
freopen("input.txt", "r", stdin);
Read(n);
scanf("%s",c+1);
Suffix_Automaton::init();
for_up(i, 1, n)
f[i] = add(f[i-1], extend(c[i]-'a'));
for_up(i, 1, n) {
f[i] = add(f[i], f[i-1]);
Write(f[i]);
putchar('\n');
}
return 0;
}

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. CountDownLatch详解

    功能描述 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 常见用法 多个人等一个信号后继续执行操作.例如5个运动员,等一个发令员的枪响. 一个人等多个人的信号. ...

  2. SQL使用CASE 语句

    CASE 语句可以在SELECT 子句和ORDER BY 子句中使用 CASE语句分为两种Case Simple Expression and Case Search Expression Case ...

  3. 《Think in Java》(十三)字符串

    学完这章后,对 Java 字符串有了重新的认识.自己也看了下 CharSequence,String,StringBuilder,StringBuffer 等类的实现代码.

  4. 值类型的TryParse

    值类型(Struct(如:DateTime).基本类型(如:double).枚举类型)的TryParse方法,通常可使用该方法将“字符串”转换为当前类型,并out出.比如:日期格式的字符串   转换为 ...

  5. dva-loading使用方法

    1. 下载dva-loading npm install dva-loading --save 2. 导入并使用dva-loading import createLoading from 'dva-l ...

  6. no persistent classes found for query class: FROM com.vrv.paw.domain.User

    在整合Spring+Hibernate时报该错误,sessionFactory配置如下: <bean id="sessionFactory" class="org. ...

  7. python 连接操作 各类数据库

    转载自MySQL Loners 一,python 操作 MySQL:详情见:这里 #!/bin/env python # -*- encoding: utf-8 -*- #-------------- ...

  8. C# 设计模式巩固 - 抽象工厂模式

    前言   继续工厂模式高级版,抽象工厂模式.抽象工厂模式其实是从工厂方法模式拓展而来.在实际的生活中一个工厂的产品不可能是单一的,肯定是多种系列的产品. 介绍 - 抽象工厂模式 定义:(摘自百度百科~ ...

  9. 老鼠走迷宫(1)输出唯一路径(C语言)

    需求 有一个迷宫,在迷宫的某个出口放着一块奶酪.将一只老鼠由某个入口处放进去,它必须穿过迷宫,找到奶酪.请找出它的行走路径. STEP 1 题目转化 我们用一个二维数组来表示迷宫,用2表示迷宫的墙壁, ...

  10. 《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...