BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并
题目链接:
题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字符到第$r$个字符组成的子串)。
首先考虑$l=1,r=|S|$的情况,对$T$串建立后缀自动机,可以知道$T$串本质不同的子串个数就是后缀自动机上每个点的$len[i]-len[pre[i]]$($len[i]$代表这个点所能表示的最长串长度),这也就是后缀自动机上每个点贡献的子串个数。对于每个点它贡献的串显然是它的后缀,但这些后缀中会有一些是$S$串的子串,所以就需要减掉这部分的贡献。我们再对$S$建立后缀自动机并将$T$串在$S$串上匹配求出$mx[i]$表示$T[1,i]$的最长后缀是$S$串的子串的长度。匹配时我们用$cnt$记录匹配完$T$串每个字符$i$之后得到的$mx[i]$,显然如果能往下走就$cnt++$,否则就跳父亲节点直到能往下走为止(假设跳父亲节点跳到$p$),然后将$cnt$置成$len[p]$再继续往下走,这样就能得到$T$串中每个点的$mx[i]$。对于$T$串的后缀自动机上每个点$i$再记录它$endpos$集合中最小的位置$pos[i]$(因为$endpos$集合中任意位置的后缀都相同,所以随便哪个都行,但第一个显然在建后缀自动机时就能记录)。那么最后的答案就是$ans=\sum max(0,len[i]-max(mx[pos[i]],len[pre[i]]))$,其中$i$为后缀自动机上的所有节点。
现在考虑$l,r$任意的情况,显然$mx[i]$要变成$T[1,i]$的最长后缀是$S[l,r]$的子串的最长长度。所以我们需要维护$S$串后缀自动机上每个节点的$endpos$集合,对于$S$串后缀自动机上每个节点动态开点建一棵线段树存这个节点的$endpos$集合,然后在$pre$树上从下往上线段树合并即可维护出每个点的$endpos$集合。那么当$T$串在$S$串后缀自动机上匹配时,我们需要知道接下来要往下走的节点的$endpos$集合中是否有在$[l+cnt,r]$之中的。如果有就往下走并$cnt++$,否则一点点减小$cnt$并继续在线段树上查找,当$cnt$减小到$len[pre[i]]$时说明当前点不能匹配$T$串的下一个字符,这时就要跳到$pre[i]$再重复上述操作,最后求答案的部分与$l=1,r=|S|$的情况相同。注意在线段树合并时因为要保留被合并的树,所以在合并时要新开节点。
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll ans;
int n,q;
int L,R;
int tot;
char s[500010];
char t[500010];
int to[1000010];
int mx[500010];
int head[1000010];
int next[1000010];
int root[1000010];
void add(int x,int y)
{
next[++tot]=head[x];
head[x]=tot;
to[tot]=y;
}
namespace segment_tree
{
int cnt;
int ls[36000010];
int rs[36000010];
void insert(int &rt,int l,int r,int k)
{
if(!rt)
{
rt=++cnt;
}
if(l==r)
{
return ;
}
int mid=(l+r)>>1;
if(k<=mid)
{
insert(ls[rt],l,mid,k);
}
else
{
insert(rs[rt],mid+1,r,k);
}
}
int merge(int x,int y)
{
if(!x||!y)
{
return x+y;
}
int rt=++cnt;
ls[rt]=merge(ls[x],ls[y]);
rs[rt]=merge(rs[x],rs[y]);
return rt;
}
int query(int rt,int l,int r,int L,int R)
{
if(L>R)
{
return 0;
}
if(!rt)
{
return 0;
}
if(L<=l&&r<=R)
{
return 1;
}
int mid=(l+r)>>1;
int res=0;
if(L<=mid)
{
res|=query(ls[rt],l,mid,L,R);
}
if(R>mid)
{
res|=query(rs[rt],mid+1,r,L,R);
}
return res;
}
}
void dfs(int x)
{
for(int i=head[x];i;i=next[i])
{
dfs(to[i]);
root[x]=segment_tree::merge(root[x],root[to[i]]);
}
}
namespace SAM_S
{
int trs[1000010][26];
int len[1000010];
int pre[1000010];
int cnt=1;
int last=1;
void insert(int x,int y)
{
int p=last;
int np=++cnt;
last=np;
len[np]=len[p]+1;
segment_tree::insert(root[np],1,n,y);
for(;p&&!trs[p][x];p=pre[p])
{
trs[p][x]=np;
}
if(!p)
{
pre[np]=1;
}
else
{
int q=trs[p][x];
if(len[q]==len[p]+1)
{
pre[np]=q;
}
else
{
int nq=++cnt;
pre[nq]=pre[q];
memcpy(trs[nq],trs[q],sizeof(trs[q]));
pre[np]=pre[q]=nq;
len[nq]=len[p]+1;
for(;p&&trs[p][x]==q;p=pre[p])
{
trs[p][x]=nq;
}
}
}
}
void build()
{
for(int i=1;i<=n;i++)
{
insert(s[i]-'a',i);
}
for(int i=2;i<=cnt;i++)
{
add(pre[i],i);
}
dfs(1);
}
}
namespace SAM_T
{
int trs[1000010][26];
int len[1000010];
int pre[1000010];
int pos[1000010];
int cnt;
int last;
void initial()
{
memset(trs,0,(cnt+2)*sizeof(trs[0]));
cnt=last=1;
}
void insert(int x,int y)
{
int p=last;
int np=++cnt;
last=np;
len[np]=len[p]+1;
pos[np]=y;
for(;p&&!trs[p][x];p=pre[p])
{
trs[p][x]=np;
}
if(!p)
{
pre[np]=1;
}
else
{
int q=trs[p][x];
if(len[q]==len[p]+1)
{
pre[np]=q;
}
else
{
int nq=++cnt;
pre[nq]=pre[q];
pos[nq]=pos[q];
memcpy(trs[nq],trs[q],sizeof(trs[q]));
pre[np]=pre[q]=nq;
len[nq]=len[p]+1;
for(;p&&trs[p][x]==q;p=pre[p])
{
trs[p][x]=nq;
}
}
}
}
void build()
{
initial();
int tlen=strlen(t+1);
for(int i=1;i<=tlen;i++)
{
insert(t[i]-'a',i);
}
}
}
void solve(int L,int R)
{
ans=0;
int tlen=strlen(t+1);
int now=1;
int res=0;
for(int i=1;i<=tlen;i++)
{
while(1)
{
if(!segment_tree::query(root[SAM_S::trs[now][t[i]-'a']],1,n,L+res,R))
{
if(!res)
{
break;
}
res--;
if(res==SAM_S::len[SAM_S::pre[now]])
{
now=SAM_S::pre[now];
}
}
else
{
res++;
now=SAM_S::trs[now][t[i]-'a'];
break;
}
}
mx[i]=res;
}
for(int i=2;i<=SAM_T::cnt;i++)
{
ans+=max(0,SAM_T::len[i]-max(SAM_T::len[SAM_T::pre[i]],mx[SAM_T::pos[i]]));
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
SAM_S::build();
scanf("%d",&q);
while(q--)
{
scanf("%s",t+1);
scanf("%d%d",&L,&R);
SAM_T::build();
solve(L,R);
printf("%lld\n",ans);
}
}
BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并的更多相关文章
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)
LOJ 洛谷 BZOJ 考虑\(l=1,r=|S|\)的情况: 对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\). 因为要去重,对\(T\ ...
- luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并
其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(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 ...
随机推荐
- Groovy语言学习--语法基础(3)
侧重点可能是groovy metaClass基元类的概念,有点像java的反射,因为java反射目前基本也没研究过,就mark一下,后续若有用到就深入研究一下. 基础语法的东西貌似差不多八九不离十了, ...
- 面试:用 Java 逆序打印链表
昨天的 Java 实现单例模式 中,我们的双重检验锁机制因为指令重排序问题而引入了 volatile 关键字,不少朋友问我,到底为啥要加 volatile 这个关键字呀,而它,到底又有什么神奇的作用呢 ...
- 【Python】动手分析天猫内衣售卖数据,得到你想知道的信息
大家好,希望各位能怀着正直.严谨.专业的心态观看这篇文章.ヾ(๑╹◡╹)ノ" 接下来我们尝试用 Python 抓取天猫内衣销售数据,并分析得到中国女性普遍的罩杯数据.最受欢迎的内衣颜色是什么 ...
- [Linux]Debian 9重启DNS重置问题
先编辑/etc/resolv.conf, 添加一个DNS, 比如114.114.114.114 然后sudo apt-get install resolvconf 然后编辑/etc/resolvcon ...
- ORM 多表操作查询及增删改查
------------------------------------------只有对前途乐观的人,才能不怕黑暗,才能有力量去创造光明.乐观不是目的,而是人生旅途中的一种态度. 多表操作 创建模型 ...
- Vue(三)之前端路由
01-前端路由 1.前端路由的实现原理 vue+vue-router 主要来做单页面应用(Single Page Application) 为什么我们要做单页面应用? (1)传统的开发方式 url改变 ...
- 01-HTML介绍
1.WEB标准 web准备介绍: w3c:万维网联盟组织,用来制定web标准的机构(组织) web标准:制作网页遵循的规范 web准备规范的分类:结构标准.表现标准.行为标准. 结构:html.表示: ...
- ARC 066D Xor Sum AtCoder - 2272 (打表找规律)
Problem Statement You are given a positive integer N. Find the number of the pairs of integers u and ...
- 通过Webstorm上传代码到Github、更新代码后同步到github及克隆github代码到本地的方法
导读: Github做为IT爱好者分享代码的一个知名的平台,广受大家喜欢,那么我们平时该怎么将自己写的代码上传到github上面保存并且提供给其他人参考? 我想方法不外乎如下几个: 1.直接在gith ...
- fun = [lambda x: x*i for i in range(4)] 本质解析/原理,LEGB规则 闭包原理
命名空间,闭包原理,参考点击本文 一.问题描述 fun = [lambda x: x*i for i in range(4)] for item in fun: print(item(1)) 上述式子 ...