Luogu4770 NOI2018你的名字(后缀自动机+线段树合并)
先考虑l=1,r=n,并且不要求本质不同的情况。对原串建SAM,将询问串在上面跑,得到每个前缀的最长匹配后缀即可得到答案。
然后考虑本质不同。对询问串也建SAM,统计每个节点的贡献,得到该点right集合中任意一个的匹配长度即可。
然后考虑原问题。我们需要求的仍然只是每个前缀的最长匹配后缀。通过线段树合并得到原串SAM每个点的right集合,同样将询问串在上面跑,跑的时候根据所达点right集合在给定区间中的最大值得到该点极限匹配长度,判断是否能在该点匹配(即极限匹配长度是否不小于该点所表示的最短长度),若不能则跳fail。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 1000010
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
char s[N];
int Q,match[N],tmp[N],root[N],cnt;
struct data{int l,r,x;
}tree[N*40];
void ins(int &k,int l,int r,int x)
{
tree[++cnt]=tree[k],k=cnt;tree[k].x=max(tree[k].x,x);
if (l==r) return;
int mid=l+r>>1;
if (x<=mid) ins(tree[k].l,l,mid,x);
else ins(tree[k].r,mid+1,r,x);
}
int merge(int x,int y,int l,int r)
{
if (!x||!y) return x|y;
int k=++cnt;
tree[k].x=max(tree[x].x,tree[y].x);
if (l<r)
{
int mid=l+r>>1;
tree[k].l=merge(tree[x].l,tree[y].l,l,mid);
tree[k].r=merge(tree[x].r,tree[y].r,mid+1,r);
}
return k;
}
int query(int k,int l,int r,int x,int y)
{
if (!k) return 0;
if (l==x&&r==y) return tree[k].x;
int mid=l+r>>1;
if (y<=mid) return query(tree[k].l,l,mid,x,y);
else if (x>mid) return query(tree[k].r,mid+1,r,x,y);
else
{
int u=query(tree[k].r,mid+1,r,mid+1,y);
if (u) return u;else return query(tree[k].l,l,mid,x,mid);
}
}
struct SAM
{
int n,son[N][26],fail[N],len[N],id[N],q[N],f[N],cnt,last;
int newnode(){cnt++;memset(son[cnt],0,sizeof(son[cnt]));fail[cnt]=len[cnt]=0;return cnt;}
void extend(int c)
{
int x=newnode(),p=last;last=x;len[x]=len[p]+1;id[len[x]]=x;
while (!son[p][c]&&p) son[p][c]=x,p=fail[p];
if (!p) fail[x]=1;
else
{
int q=son[p][c];
if (len[p]+1==len[q]) fail[x]=q;
else
{
int y=newnode();
len[y]=len[p]+1;
memcpy(son[y],son[q],sizeof(son[q]));
fail[y]=fail[q],fail[q]=fail[x]=y;
while (son[p][c]==q) son[p][c]=y,p=fail[p];
}
}
}
void build(char *s)
{
cnt=0;last=1;newnode();n=strlen(s+1);
for (int i=1;i<=n;i++) extend(s[i]-'a');
for (int i=0;i<=n;i++) tmp[i]=0;
for (int i=1;i<=cnt;i++) tmp[len[i]]++;
for (int i=1;i<=n;i++) tmp[i]+=tmp[i-1];
for (int i=1;i<=cnt;i++) q[tmp[len[i]]--]=i;
}
void run(char *s,int l,int r)
{
int m=strlen(s+1);
int k=1,t=0;
for (int i=1;i<=m;i++)
{
int c=s[i]-'a';
while (!son[k][c]&&k) k=fail[k],t=len[k];
if (!k) k=1,t=0;
else k=son[k][c],t++;
while (k)
{
int u=query(root[k],1,n,l,r);
if (u-l+1<=len[fail[k]]) {k=fail[k],t=len[k];continue;}
t=min(t,u-l+1);break;
}
if (!k) k=1,t=0;
match[i]=t;
}
}
void getright()
{
for (int i=1;i<=n;i++) ins(root[id[i]],1,n,i);
for (int i=cnt;i>=1;i--)
{
int x=q[i];
root[fail[x]]=merge(root[fail[x]],root[x],1,n);
}
}
ll calc()
{
ll ans=0;
for (int i=1;i<=cnt;i++) f[i]=N;
for (int i=1;i<=n;i++) f[id[i]]=match[i];
for (int i=cnt;i>=1;i--)
{
int x=q[i];
ans+=max(0,len[x]-max(len[fail[x]],f[x]));
f[fail[x]]=min(f[fail[x]],f[x]);
}
return ans;
}
}S,T;
int main()
{
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
scanf("%s",s+1);S.build(s);S.getright();
Q=read();
while (Q--)
{
scanf("%s",s+1);
int l=read(),r=read();
S.run(s,l,r);
T.build(s);printf(LL,T.calc());
}
return 0;
}
Luogu4770 NOI2018你的名字(后缀自动机+线段树合并)的更多相关文章
- luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并
其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(T\) ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并
题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...
- BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)
LOJ 洛谷 BZOJ 考虑\(l=1,r=|S|\)的情况: 对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\). 因为要去重,对\(T\ ...
- NOI 2018 你的名字 (后缀自动机+线段树合并)
题目大意:略 令$ION2017=S,ION2018=T$ 对$S$建$SAM$,每次都把$T$放进去跑,求出结尾是i的前缀串,能匹配上$S$的最长后缀长度为$f_{i}$ 由于$T$必须在$[l,r ...
- [NOI2018]你的名字(后缀自动机+线段树)
题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)
https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...
- 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)
模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...
随机推荐
- Linux fdisk命令创建扩展分区过程
[root@localhost ~]# fdisk /dev/sdb …省略部分输出… Command (m for help): n #新建立分区 Command action e extended ...
- LeetCode 222. 完全二叉树的节点个数(Count Complete Tree Nodes)
题目描述 给出一个完全二叉树,求出该树的节点个数. 说明: 完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位 ...
- 基于golang的websocket通信实现
代码: https://gitee.com/knox_xzk/websocket
- webpack介绍和使用
一webpack介绍1由来2介绍3作用4拓展说明5webpack整体认知二webpack安装1安装node2安装cnpm3安装nrm的两种方法4安装webpack三webpack配置0搭建项目结构1初 ...
- H2O框架简介
H2O框架简介H2O是开源的,分布式的,基于内存的,可扩展的机器学习和预测分析框架,适合在企业环境中构建大规模机器学习模型. H2O核心代码使用Java编写,数据和模型通过分布式 Key/Value ...
- 调用百度地图API的总结
因为项目要用到百度地图,所以先摸索了一下,各种功能官方都有文档,点击可查看,文章的话我就直接写我用到的功能例子了,要用可以直接复制粘贴~ 一.主要涉及到的几个接口(先申请密钥): 1.技术一:坐标转换 ...
- Linux给力的Shell命令
查看目录下面的所有目录 ls /dev/ -F |grep "/" bus/ disk/ input/ 说明: ls -F命令将文件和目录后面添加一个特定的符号; ...
- transition css3 渐变效果
div { width:100px; transition: width 2s; -moz-transition: width 2s; /* Firefox 4 */ -webkit-transiti ...
- iOS-NSTimer计时器
(3) 计时器NSTimer + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget sel ...
- thinkphp5 验证器 validate 和 layer
首先tp5的验证器使用特方便 设置规则即可通用 首先页面html(layer 配合) 毕竟是后端 尽量用一些成熟的前台框架 之前用boostrap $.ajax({ url:'/index/Regi ...