题意

  你有一个字符串,你需要支持两种操作:

  1:在字符串的末尾插入一个字符 \(c\)

  2:询问当前字符串的 \([l,r]\) 子串中的不同子串个数

  为了加大难度,操作会被加密(强制在线)。

  \(n,m\le 50000\),空间 \(\text{1GB}\)

题解

  原题好像是【北京集训 2017 String】,题意:给你一个模板串 \(T\),有 \(Q\) 组询问,每组询问给出 \(2\) 个正整数 \(l,r\),请你找出 \(T[l...r]\) 中出现至少 \(2\) 次的最长子串。\(|T|,Q\le 10^5\)。(这题以前欧神好像让 gdc 写过)

subtask 1(\(n,m\le 1500\))

  直接建一个后缀自动机暴力跑查询 好像是 \(O(n^3)\) 的(本文假设 \(n,m,n+m\) 同阶)……

  如果你刚学 SAM 的话,可以想到对字符串建 \(n\) 个 SAM,第 \(i\) 个 SAM 插入字符串的第 \(i\) 到 \(n\) 位,这样第 \(i\) 个 SAM 就可以预处理出 \([i,i],[i,i+1],\cdots,[i,n]\) 这些询问区间。考虑在线操作,对于插入,设字符串当前长度为 \(len\),则需要对 \(len\) 个后缀自动机 \(\text{extend}\) 一位字符,并更新 \(len\) 个区间的答案;对于查询,\(O(1)\) 取预处理的答案即可。

  时间复杂度 \(O(n^2)\),空间复杂度 \(O(26n^2)\),如果 SAM 不动态开点,请把所有变量开成 \(\text{short}\),开成 \(\text{int}\) 或 \(\text{long long}\) 会被卡空间。

subtask 2(离线)

  鸽了(看完在线做法你大概也猜到离线做法了)

subtask all

  不难发现这题的操作 1 就是 \(\text{SAM}\) 的 \(\text{extend}\) 过程。

  设进行一次 1 操作后字符串的长度为 \(len\),则当次 \(\text{extend}\) 会使字符串增加以第 \(len\) 位为结尾的所有子串。

  这些子串对应的就是 \(\text{SAM}\) 的 \(\text{parents}\) 树上一个叶子节点到根的链,且那个叶子节点就是你新建的表示子串 \(S_{1\cdots len}\) 的节点。

  于是我们考虑用 \(\text{LCT}\) 动态维护 \(\text{SAM}\) 的 \(\text{parents}\) 树。

  但有个问题:哪些点放在同一条重链上(同一个 \(\text{splay}\) 中)呢?

  这个问题似乎不太好想,我们先跳过。

  我们考虑如何维护某个区间 \([l,r]\) 中的不同子串数量。

  这个问题可以简化为给你 \(n\) 个数,每次询问某个区间 \([l,r]\) 中有多少个不同的数。

  显然可以使用容斥法,用总数量减去那些非最后一次出现的数。考虑预处理答案,从前往后依次加入每个数,加入第 \(i\) 个数即将主席树的第 \(i\) 个版本的第 \(i\) 位加 \(1\)。若该数在之前出现过,设上一次出现的位置为 \(lst\),则在主席树的第 \(i\) 个版本的第 \(j\) 位减 \(1\)。

  那么区间内不同子串数量也可以类似地用主席树解决。

  考虑暴力,做法类似前一题:考虑 \(\text{extend}\) 到第 \(len\) 位时,检查所有以第 \(len\) 位为结尾的子串是否在之前出现过,若出现过,设这个子串上一次出现位置的左端点为 \(x\),则对于所有 \(r\ge len,\space l\le x\) 的询问,答案要从子串总数中 \(-1\),放到主席树上 就是主席树的第 \(len\) 个版本的第 \(x\) 位减 \(1\)。

  我们之前说过,所有以第 \(len\) 位为结尾的子串 是 \(\text{parents}\) 树上的一条链。在 \(\text{LCT}\) 上取一条链的信息 是经典操作,而且这里的链顶就是根,所以我们只要把链底节点 \(\text{access}\) 一下就行了。在 \(\text{access}\) 时我们需要链上每个点上一次出现的位置 \(lst\),由于一条重链上所有点的 \(lst\) 相同,故 \(\text{access}\) 后在链顶打个 \(tag\),下次访问这条链时 \(\text{pushdown}\) 即可。有了每个点的 \(lst\),我们就可以在 \(\text{access}\) 时对每个点在主席树上进行修改。查询区间 \([l,r]\) 的答案时,对主席树的第 \(r\) 个版本的 \([l,r]\) 区间求和即可。

  由此可知,\(\text{LCT}\) 中一条重链存的是所有上一次出现位置相同的 SAM 节点。

  然后就是码码码了。

  注意这题的主席树是区间修改,所以每新建一个版本最多会增加 \(4\log n\) 个点而不是 \(\log n\) 个点,所以主席树大小要开到 \(80n\) 而不是 \(20n\)。

  复杂度 \(O(n\log^2 n)\)。

  (我)翻车的一个地方:访问 \(x\) 号点时,一定要把它所在的重链顶端的 \(tag\) \(\text{pushdown}\) 到 \(x\) 号点的儿子,即 \(x\) 号点要 \(\text{pushdown}\)!

  (好吧其实是个傻逼错误,那大家无视好了)

#include<bits/stdc++.h>
#define ll long long
#define N 200005
using namespace std;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
if(f) return x; return 0-x;
}
int n,m,len;
char s[50001];
int rt[N];
namespace PT{
struct Tree{int l,r,tag; ll sum;}tr[N*80];
int cnt=0;
void mdf(int& o, int l, int r, int L, int R, int v){
tr[++cnt]=tr[o], o=cnt;
tr[o].sum += (ll)(min(r,R)-max(l,L)+1) * v;
if(L<=l && r<=R){tr[o].tag+=v; return;}
int mid=l+r>>1;
if(L<=mid) mdf(tr[o].l,l,mid,L,R,v);
if(R>mid) mdf(tr[o].r,mid+1,r,L,R,v);
}
ll query(int o, int l, int r, int L, int R){
if(!o) return 0;
if(L<=l && r<=R) return tr[o].sum;
int mid=l+r>>1; ll res = (ll)(min(r,R)-max(l,L)+1) * tr[o].tag;
if(L<=mid) res+=query(tr[o].l,l,mid,L,R);
if(R>mid) res+=query(tr[o].r,mid+1,r,L,R);
return res;
}
}
namespace LCT{
int son[N][2],fa[N],len[N],lst[N],tag[N],stk[N],top;
inline bool isRoot(int x){return son[fa[x]][0]!=x && son[fa[x]][1]!=x;}
inline bool idf(int x){return son[fa[x]][1]==x;}
inline void mark(int x, int y){lst[x]=tag[x]=y;}
void pushdown(int x){
if(tag[x]){
if(son[x][0]) mark(son[x][0],tag[x]);
if(son[x][1]) mark(son[x][1],tag[x]);
tag[x]=0;
}
}
inline void connect(int x, int f, int fx){
fa[x]=f, son[f][fx]=x;
}
void rotate(int x){
int y=fa[x], z=fa[y], idf_x=idf(x), idf_y=idf(y), B=son[x][idf_x^1];
if(!isRoot(y)) connect(x,z,idf_y);
else fa[x]=z;
connect(B,y,idf_x), connect(y,x,idf_x^1);
}
void splay(int x){
stk[top=1]=x;
for(int i=x; !isRoot(i); i=fa[i]) stk[++top]=fa[i];
for(; top; --top) pushdown(stk[top]);
while(!isRoot(x)){
int f=fa[x];
if(!isRoot(f)) rotate(idf(f)==idf(x) ? f : x);
rotate(x);
}
}
void access(int x, int id){
for(int y=0; ; x=fa[y=x]){
splay(x);
if(lst[x]) PT::mdf(rt[id],1,n,lst[x]-len[x]+1,lst[x]-len[fa[x]],1);
son[x][1]=y;
if(!fa[x]) break;
}
mark(x,id);
}
inline int get(int x){splay(x); return lst[x];}
void link(int x, int y){splay(x), fa[x]=y;}
}
namespace SAM{
int ch[N][26],fa[N],len[N],tot,lst;
inline void init(){tot=lst=1;}
void extend(int c, int id){
int u=lst, v=++tot; len[v]=len[u]+1, LCT::len[v]=len[v];
for(; u && !ch[u][c]; u=fa[u]) ch[u][c]=v;
if(!u) fa[v]=1, LCT::link(v,1), LCT::access(v,id);
else{
int w=ch[u][c];
if(len[w]==len[u]+1) fa[v]=w, LCT::link(v,w), LCT::access(v,id);
else{
int t=++tot; len[t]=len[u]+1, LCT::len[t]=len[t], LCT::lst[t]=LCT::get(w);
memcpy(ch[t],ch[w],sizeof ch[w]);
fa[t]=fa[w], fa[w]=fa[v]=t;
LCT::link(t,fa[t]), LCT::link(v,t), LCT::access(v,id), LCT::link(w,t);
for(; u && ch[u][c]==w; u=fa[u]) ch[u][c]=t;
}
}
lst=v;
}
}
int main(){
int type=read();
scanf("%s",s+1), len=strlen(s+1);
m=read();
n=len+m;
SAM::init();
for(int i=1; i<=len; ++i) rt[i]=rt[i-1], SAM::extend(s[i]-'a',i);
int opt; ll l,r,ans=0; char c[1];
while(m--){
opt=read();
if(opt==1){
scanf("%s",c);
++len, rt[len]=rt[len-1];
SAM::extend(((ll)c[0]-'a'+ans*type)%26, len);
}
else{
l = ((ll)read()-1+ans*type) % len + 1, r = ((ll)read()-1+ans*type) % len + 1;
printf("%lld\n", ans = (ll)(r-l+2)*(r-l+1)/2 - PT::query(rt[r],1,n,l,r));
}
}
return 0;
}

【2017 北京集训 String 改编版】子串的更多相关文章

  1. 张小龙在2017微信公开课PRO版讲了什么(附演讲实录和2016微信数据报告)

    今天2017微信公开课PRO版在广州亚运城综合体育馆举行,这次2017微信公开课大会以“下一站”为主题,而此次的微信公开课的看点大家可能就集中在腾讯公司高级副总裁.微信之父——张小龙的演讲上了!今天中 ...

  2. 【北京集训D2T3】tvt

    [北京集训D2T3]tvt \(n,q \le 1e9\) 题目分析: 首先需要对两条路径求交,对给出的四个点的6个lca进行分类讨论.易于发现路径的交就是这六个lca里面最深的两个所形成的链. 然后 ...

  3. 2017.11.15 String、StringBuffer、StringBuilder的比较(todo)

    参考来自:http://blog.csdn.net/jeffleo/article/details/52194433 1.速度 一般来说,三者的速度是:StringBuilder > Strin ...

  4. (2016北京集训十)【xsy1528】azelso - 概率期望dp

    北京集训的题都是好题啊~~(于是我爆0了) 注意到一个重要的性质就是期望是线性的,也就是说每一段的期望步数可以直接加起来,那么dp求出每一段的期望就行了... 设$f_i$表示从$i$出发不回到$i$ ...

  5. HUST 1328 String (字符串前缀子串个数 --- KMP)

    题意 给定一个字符串S,定义子串subS[i] = S[0..i],定义C[i]为S中subS[i]的数量,求sigma(C[i])(0<=i<N). 思路 我们以子串结尾的位置来划分阶段 ...

  6. 2017.12.11 String 类中常用的方法

    1.编写程序将 "jdk" 全部变为大写,并输出到屏幕,截取子串"DK" 并输出到屏幕 package demo; import java.util.Scann ...

  7. 【Java必修课】判断String是否包含子串的四种方法及性能对比

    1 简介 判断一个字符串是否包含某个特定子串是常见的场景,比如判断一篇文章是否包含敏感词汇.判断日志是否有ERROR信息等.本文将介绍四种方法并进行性能测试. 2 四种方法 2.1 JDK原生方法St ...

  8. https://github.com/tensorflow/models/blob/master/research/slim/datasets/preprocess_imagenet_validation_data.py 改编版

    #!/usr/bin/env python # Copyright 2016 Google Inc. All Rights Reserved. # # Licensed under the Apach ...

  9. 2017北京国庆刷题Day5 morning

    期望得分:0+60+60=120 实际得分:0+30+60=90 令g=gcd(X11,X12,X13……) 则行列式可能为D的充要条件为g|D 1.g|D为必要条件: 由定义来算行列式的时候,每一项 ...

随机推荐

  1. vscode 配置 GOPATH

    我已经放弃goland开发工具了,所以用万能的vscode 作为我学习go的开始: 按照网上的教程一步步配置了GOROOT,GOPATH等等,执行go env 也是没有问题的,但是当我用vscode写 ...

  2. 【DataBase】mysql连接错误:Cannot get hostname for your address

    问题 环境:win7 + 64Bit + 本地mysql5.6 问题:navicat连接本地mysql数据库,提示“Cannot get hostname for your address”,但是连接 ...

  3. ASP.NET(C#) 使用 SqlBulkCopy 实现批量插入SQL(快捷简单)

    业务需要,系统在处理数据时,每暂存一列数据将他插入到右侧的表格中,再执行批量保存,如图所示: //以前的做法可能是生成一堆 insert into xx values xxx 的sql语句,在程序中去 ...

  4. 03-初识JavaScript

    一. JavaScript简介(了解) 1. JavaScript的历史背景介绍 布兰登 • 艾奇(Brendan Eich,1961年-),1995年在网景公司,发明的JavaScript. 一开始 ...

  5. TensorFlow实战第三课(可视化、加速神经网络训练)

    matplotlib可视化 构件图形 用散点图描述真实数据之间的关系(plt.ion()用于连续显示) # plot the real data fig = plt.figure() ax = fig ...

  6. netcore程序部署 ubuntu 16.0.4 报错 The type initializer for 'System.Net.Http.CurlHandler'的解决方案

    最近业务扩展需要把netcore程序部署到ubuntu 16.0.4上,因为代码里面用到了HttpClient 请求. 部署ubuntu后一直报错 参考地址:https://github.com/do ...

  7. elasticsearch的cross_fields查询

    1.most_fields 这种方式搜索也存在某些问题 它不能使用 operator 或 minimum_should_match 参数来降低次相关结果造成的长尾效应. 2.词 peter 和 smi ...

  8. 菜鸟系列Fabric——Fabric 基本概念(1)

    Fabric 基本概念 1.区块链介绍 区块链之所以引来关注是因为比特币开源项目,尤其是比特币价值的飙升,让大家开始关注数字货币以及相关技术.那么区块链究竟是什么? 1.1 区块链定义 狭义上,区块链 ...

  9. 【Spring AOP】Spring AOP的使用方式【Q】

    Spring AOP的三种使用方式 经典AOP使用方式 改进XML配置方式 基于注解的方式 第1种方式可以作为理解spring配置AOP的基础,是最原始的配置方式,也体现了spring处理的过程. 使 ...

  10. Linx

    1. 2. 2. 3. 5. Vi 猜数字 第二十个裴伯拉数字 1 1 2 3 5 8 2 3 求小于3000的裴伯拉数列 4 5 递归方式1到100 和 6 7 100 以内奇数.偶数和 8 Sss ...