[NOI2018]你的名字(SAM+线段树合并)
考虑l=1,r=n的68分,对S和T建SAM,对T的SAM上的每个节点,计算它能给答案带来多少贡献。
T上节点x代表的本质不同的子串数为mx[x]-mx[fa[x]],然后需要去掉所代表子串与S的最长公共子串的长度。
从1到length(T)扫一遍,SAM基本操作求出每个前缀与S的最长公共子串。
答案为$\sum_{i=1}^{cnt}max(0,mx[x]-max(mx[fa[x]],len[tag[x]]))$,其中tag[x]是x所代表的子串在T中的第一个出现位置,len[i]为T的前缀i与S的最长公共子串。
再考虑l,r任意的情况,唯一区别在于求某前缀与S的最长公共子串时,需要满足[l,r]的限制。
即若想判断当前公共子串长是否可以为len,那么当前走到的点的parent树的子树内必须存在一个串的Right在[l+len,r]中。
“某子树内是否存在[l+len,r]中的值”显然可以用主席树做,或可持久化线段树合并。
下面大概证一下线段树合并的时空复杂度:
1.由于每次合并时,若发现x与y的当前子树有一个为空则退出。这种情况每个节点最多会出现一次(合并之后这个位置就不再为空了),故这部分复杂度为$O(n\log n)$。
2.除去情况一,则x与y合并的复杂度为两个树的重合节点个数。最坏情况发生在依次将一个个只有一个节点的树合并进来,这部分显然仍然是$O(n\log n)$的。
3.不可持久化的线段树合并并不产生新节点,故空间复杂度为初始插入n个点的复杂度:$O(n\log n)$
4.可持久化的线段树,根据算法流程容易发现时空复杂度同阶,于是复杂度也为$O(n\log n)$
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lson ls[x],L,mid
#define rson rs[x],mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
typedef long long ll;
using namespace std; const int N=,M=;
char s[N],t[N];
int n,m,nd,Q,l,r,len[N],rt[N],id[N],ls[M],rs[M]; struct SAM{
int nd,lst,mx[N],fa[N],pos[N],son[N][]; void init(){
nd=; lst=;
rep(i,,m*) fa[i]=mx[i]=;
rep(i,,m*) rep(j,,) son[i][j]=;
} void ext(int c,int x){
int p=lst,np=lst=++nd; pos[np]=x; mx[np]=mx[p]+;
while (p && !son[p][c]) son[p][c]=np,p=fa[p];
if (!p) fa[np]=;
else{
int q=son[p][c];
if (mx[q]==mx[p]+) fa[np]=q;
else{
int nq=++nd; pos[nq]=pos[q]; mx[nq]=mx[p]+;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq]=fa[q]; fa[q]=fa[np]=nq;
while (p && son[p][c]==q) son[p][c]=nq,p=fa[p];
}
}
} }S,T; bool cmp(int a,int b){ return S.mx[a]<S.mx[b]; } void ins(int &x,int L,int R,int k){
if (!x) x=++nd;
if (L==R) return;
int mid=(L+R)>>;
if (k<=mid) ins(lson,k); else ins(rson,k);
} bool que(int x,int L,int R,int l,int r){
if (!x) return ;
if (L==l && r==R) return ;
int mid=(L+R)>>;
if (r<=mid) return que(lson,l,r);
else if (l>mid) return que(rson,l,r);
else return que(lson,l,mid)|que(rson,mid+,r);
} int merge(int x,int y){
if (!x || !y) return x|y;
int now=++nd;
ls[now]=merge(ls[x],ls[y]);
rs[now]=merge(rs[x],rs[y]);
return now;
} int main(){
freopen("name.in","r",stdin);
freopen("name.out","w",stdout);
scanf("%s",s+); n=strlen(s+);
S.init(); rep(i,,n) S.ext(s[i]-'a',i),ins(rt[S.lst],,n,i);
rep(i,,S.nd) id[i]=i;
sort(id+,id+S.nd+,cmp);
for (int i=S.nd; i>; i--) rt[S.fa[id[i]]]=merge(rt[S.fa[id[i]]],rt[id[i]]);
for (scanf("%d",&Q); Q--; ){
scanf("%s%d%d",t+,&l,&r); m=strlen(t+);
int now=,x=;
T.init(); rep(i,,m) T.ext(t[i]-'a',i);
rep(i,,m){
int c=t[i]-'a';
while (){
if (!que(rt[S.son[x][c]],,n,l+now,r)){
if (!now) break; now--;
if (now==S.mx[S.fa[x]]) x=S.fa[x];
}else{ now++; x=S.son[x][c]; break; }
}
len[i]=now;
}
ll ans=;
rep(i,,T.nd) ans+=max(,T.mx[i]-max(T.mx[T.fa[i]],len[T.pos[i]]));
printf("%lld\n",ans);
}
return ;
}
[NOI2018]你的名字(SAM+线段树合并)的更多相关文章
- NOI2018 你的名字——SAM+线段树合并
题目链接在这里洛谷/LOJ 题目大意 有一个串\(S\),每次询问给你一个串\(T\),两个数\(L\)和\(R\),问你\(T\)有多少个本质不同的子串不是\(S[L,R]\)的子串 SOLUTIO ...
- 【BZOJ5417】[NOI2018]你的名字(线段树,后缀自动机)
[BZOJ5417][NOI2018]你的名字(线段树,后缀自动机) 题面 BZOJ 洛谷 题解 首先考虑\(l=1,r=|S|\)的做法,对于每次询问的\(T\)串,暴力在\(S\)串的\(SAM\ ...
- 【NOI2018】你的名字(SAM & 线段树合并)
Description Hint Solution 不妨先讨论一下无区间限制的做法. 首先"子串"可以理解为"前缀的后缀",因此我们定义一个 \(\lim(i) ...
- UOJ#395. 【NOI2018】你的名字 字符串,SAM,线段树合并
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ395.html 题解 记得同步赛的时候这题我爆0了,最暴力的暴力都没调出来. 首先我们看看 68 分怎么做 ...
- P4770-[NOI2018]你的名字【SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/P4770 题目大意 给出一个长度为\(n\)的字符串\(S\).\(q\)次询问给出一个串\(T\)和一个区间\([ ...
- CF1037H Security——SAM+线段树合并
又是一道\(SAM\)维护\(endpos\)集合的题,我直接把CF700E的板子粘过来了QwQ 思路 如果我们有\([l,r]\)对应的\(SAM\),只需要在上面贪心就可以了.因为要求的是字典序比 ...
- luogu4770 [NOI2018]你的名字 (SAM+主席树)
对S建SAM,拿着T在上面跑 跑的时候不仅无法转移要跳parent,转移过去不在范围内也要跳parent(注意因为范围和长度有关,跳的时候应该把长度一点一点地缩) 这样就能得到对于T的每个前缀,它最长 ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/CF700E.html 题解 首先建个SAM. 一个结论:对于parent树上任意一个点x,以及它所代表的子树内任 ...
随机推荐
- [转]编译防火墙——C++的Pimpl惯用法解析
impl(pointer to implementation, 指向实现的指针)是一种常用的,用来对“类的接口与实现”进行解耦的方法.这个技巧可以避免在头文件中暴露私有细节(见下图1),因此是促进AP ...
- 2016.5.57—— Remove Duplicates from Sorted List
Remove Duplicates from Sorted List 本题收获: 指针: 不管什么指针在定义是就初始化:ListNode *head = NULL; 如果给head指针赋值为第一个no ...
- oggMonitor是什么
goldengate monitor是一套监控goldengate的软件,如果安装的ogg比较多,使用goldengate monitor可以清楚的看见全部OGG的每个进程运行状态,以及整个OGG的架 ...
- 如何和统计学家分享数据How to share data with a statistician
如何和统计学家分享数据 原文地址:https://github.com/jtleek/datasharing 将原文渣翻译的版本. 这是一份指南给需要分享数据给统计学家或者数据科学家的任何人.我认为的 ...
- 编译安装 zbar 时两次 make 带来的惊喜
为了装 php 的条形码扩展模块 php-zbarcode,先装了一天的 ImageMagick 和 zbar.也许和我装的 Ubuntu 17.10 的有版本兼容问题吧,总之什么毛病都有,apt 不 ...
- 【Andorid开发框架学习】之Volley入门
Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮.Volley特别适合数据量不大但是通信频繁的场景.在listView显示图片这方面,使用volley也是比较好的,不必 ...
- linux中查看结构体和宏
1.进入目录/usr/include cd /usr/include/ 2.生成ctags文件sudo make ctags -R 3.vim -t 结构体(宏)名称 4.找到相应的宏或者结构体 5. ...
- 数据库-mysql视图
视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,并可以将其当作表来使用 一:创建视图 create view view ...
- plsql中做计划任务
第一步: 1. 打开PLSQL后,选择节点jobs,右键新建,弹出界面后再what值中填写需要做计划的存储名加分号结束,如门诊收入存储PH_ClinicIncome(1):其中1代表医疗机构代码 间 ...
- 20155225 2016-2017-2 《Java程序设计》第十周学习总结
20155225 2016-2017-2 <Java程序设计>第十周学习总结 教材学习内容总结 22章网络 22.1网络概览 22.2超文本传输协议(HTTP) 22.2.1 HTTP请求 ...