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. 03_MySQL DQL_排序查询

    #进阶3:排序查询/*语法: select 查询列表 from 表名 [where 筛选条件] order by 排序列表 [asc|desc] 特点: 1.asc升序,desc降序, 如果都不写,默 ...

  2. sqlserver 遍历表

    use Research go ); ) NOT NULL, [mrs] date); DECLARE Table_Cursor CURSOR FOR--包含有列‘sigdate’的表 select ...

  3. 定时模块app_timer用法及常见问题—nRF5 SDK模块系列二

    app_timer是大家经常用到的一个库,app_timer的功能就是定时,也就是说,你在某一时刻启动一个app timer并设定超时时间,超时时间一到,app_timer就会回调timeout ha ...

  4. Unity教程之-UGUI一个优化效率小技巧

    无意间发现了一个小技巧.如下图所示,可以发现UGUI的Image组件的RaycastTarget勾选以后会消耗一些效率,为了节省效率就不要勾选它了,不仅Image组件Text组件也有这样的问题. 一般 ...

  5. UVA-11090 Going in Cycle!! (平均值最大回路)

    题目大意:一个n个点,m条无向边的图,求出平均权值最小的回路. 题目分析:二分枚举平均值mid,只需判断是否存在平均值小于mid的回路,即判断是否有sum(wi)<mid*k (1≤i≤k),只 ...

  6. python使用tkinter做界面之颜色

    python使用tkinter做界面之颜色       from tkinter import *colors = '''#FFB6C1 LightPink 浅粉红#FFC0CB Pink 粉红#DC ...

  7. MYSQL freedata 外联接

    主要是解决,不同生产系统里面,有不同的数据库. SQL 又不能夸系统查询表. 只能在一个系统里,可以跨不同的数据库查表. 所以会用映射 .FREEDATA 这种方式,这样A 系统 里的表更新之后,就可 ...

  8. superset dashboard 设置自动刷新

    因为发现了,自己制作了看板dashboard,却不会刷新,很奇怪. 那这样不是太傻了.难道要业务人员一个个去点吗? 一定有刷新的,然后和无头苍蝇在网上找了半天. 实际刷新的位置在这里. 具体设置有很多 ...

  9. [转载]Java导出Excel

    一.需求介绍 当前B/S模式已成为应用开发的主流,而在开发企业办公系统的过程中,常常有客户这样子要求:把系统数据库中的数据导出到Excel,用户查看报表时直接用Excel打开.或者是:用户已经习惯用E ...

  10. Mysql加载配置默认路径

    查看命令 mysqld --verbose --help|grep "Default options" -n1 输出结果 11-12:Default options are rea ...