51nod 1600 Simple KMP【后缀自动机+LCT】【思维好题】*

时间:2023-03-10 08:18:53
51nod 1600 Simple KMP【后缀自动机+LCT】【思维好题】*

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;
}