洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)
我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解……
首先,对$S$和每一次的$T$都建一个SAM
先考虑一下$l=1,r=\left| S \right|$的情况
设$lim_i$表示字符串$T[1..i]$能在$S$中匹配到的最长后缀(即$T[i-lim_i+1,i]$是$S$的子串且$lim_i$最大)(有可能不存在这个字符那么$lim_i=0$)
这个$lim_i$可以不断地在$S$的后缀自动机上跳来求出。当无法向下匹配时,一直跳parent树直到可以匹配为止
我们假设对于$T$的后缀自动机,每一个节点的$endpos$集合中所能代表的最长的字符串长度为$l_i$,$tag_i$表示该集合字符串第一次出现的结尾位置(因为集合里字符串互为后缀所以结尾相同),$fa_i$表示parent树上的父亲,$cnt$表示自动机节点总个数
那么答案就是$$ans=\sum_{i=2}^{cnt}max(0,l_i-max(l_{fa_i},lim_{tag_i}))$$
ps:这里的lim指的并不是上文的lim而是最长后缀的长度
上面式子的意思是,对于每一个节点,它不属于$S$的子串的总个数为当前节点代表的集合字符串个数减去与$S$有匹配的子串个数
然后只要在$T$的后缀自动机上枚举每一个节点就可以了
现在来考虑$l$和$r$任意的情况该怎么做
这个时候就要用线段树维护后缀自动机的$endpos$集合了(不明白这个怎么做的我简单说一下,就是搞一个动态开点线段树,如果一个节点的$endpos$集合里有某一个位置就把它加入以该点为根的树中,parent树上父亲节点的$endpos$集合必然包含儿子的$endpos$集合所以将每个点的$endpos$集合与它儿子的合并。然后查询这个节点是否有$endpos$位于某个区间中只要在线段树上查询看看这个区间代表的节点是否被开出来过就好了(因为线段树上只有存在的位置的节点被开出来过))
我们在处理$lim_i$集合的时候要注意,要判断当前节点是否在$S[l..r]$区间中出现过。设$p$为当前在$S$的自动机上跑到的节点,$len$表示匹配了$[i-len+1..i]$,因为要看能否转移到下一个节点,所以要匹配$[i-len,i]$,且$[l+len,r]$中有后继节点的$endpos$存在才行(可能说的烦了点,仔细想想为什么)
差不多就这样
//minamoto
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(ll x){
if(C><<)Ot();
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e6+,M=2e7+,inf=0x3f3f3f3f;
int q,n,m,rt[N],lim[N];char S[N];ll ans;
namespace tree{
int cnt,L[M],R[M];
void ins(int &p,int l,int r,int x){
if(!p) p=++cnt;
if(l==r) return;
int mid=(l+r)>>;
if(x<=mid) ins(L[p],l,mid,x);
else ins(R[p],mid+,r,x);
}
int merge(int x,int y){
if(!x||!y) return x+y;
int p=++cnt;
L[p]=merge(L[x],L[y]);
R[p]=merge(R[x],R[y]);
return p;
}
bool query(int p,int l,int r,int ql,int qr){
if(!p||ql>qr) return false;
if(ql<=l&&qr>=r) return true;
int mid=(l+r)>>;
if(ql<=mid&&query(L[p],l,mid,ql,qr)) return true;
if(qr>mid&&query(R[p],mid+,r,ql,qr)) return true;
return false;
}
}
namespace SAM{
int cnt=,last=;
int ch[N][],l[N],fa[N],c[N],a[N],in[N];
void ins(int c){
int p=last,np=++cnt;last=np,l[np]=l[p]+,in[np]=;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else{
int q=ch[p][c];
if(l[q]==l[p]+) fa[np]=q;
else{
int nq=++cnt;l[nq]=l[p]+;
memcpy(ch[nq],ch[q],sizeof(int)*());
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
inline void calc(){
for(int i=;i<=n;++i) ins(S[i]-'a');
for(int i=;i<=cnt;++i) ++c[l[i]];
for(int i=;i<=cnt;++i) c[i]+=c[i-];
for(int i=;i<=cnt;++i) a[c[l[i]]--]=i;
for(int i=cnt,p;i;--i){
p=a[i];
if(in[p]) tree::ins(rt[p],,n,l[p]);
rt[fa[p]]=tree::merge(rt[fa[p]],rt[p]);
}
}
}
namespace solve{
int cnt=,last=;
int ch[N][],l[N],fa[N],c[N],a[N],tag[N];
inline void init(){
cnt=last=,memset(ch[],,sizeof(int)*());
}
inline int newnode(){
++cnt;memset(ch[cnt],,sizeof(int)*());return cnt;
}
void ins(int c){
int p=last,np=newnode();last=np,tag[np]=l[np]=l[p]+;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else{
int q=ch[p][c];
if(l[q]==l[p]+) fa[np]=q;
else{
int nq=newnode();l[nq]=l[p]+,tag[nq]=tag[q];
memcpy(ch[nq],ch[q],sizeof(int)*());
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
ll solve(){
int L,R;
scanf("%s%d%d",S+,&L,&R);
init();
m=strlen(S+);
for(int len=,p=,i=;i<=m;++i){
int c=S[i]-'a';
ins(c);
while(true){
if(SAM::ch[p][c]&&tree::query(rt[SAM::ch[p][c]],,n,L+len,R)){
++len,p=SAM::ch[p][c];break;
}
if(len==) break;
--len;
if(len==SAM::l[SAM::fa[p]]) p=SAM::fa[p];
}
lim[i]=len;
}
ans=;
for(int i=;i<=cnt;++i)
ans+=max(,l[i]-max(l[fa[i]],lim[tag[i]]));
return ans;
}
}
int main(){
// freopen("testdata.in","r",stdin);
scanf("%s",S+);
n=strlen(S+);
SAM::calc();
scanf("%d",&q);
while(q--) print(solve::solve());
Ot();
return ;
}
洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)的更多相关文章
- 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]
传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...
- 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\ ...
- [NOI2018]你的名字(后缀自动机+线段树)
题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...
- luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并
其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(T\) ...
- UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)
NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...
- NOI 2018 你的名字 (后缀自动机+线段树合并)
题目大意:略 令$ION2017=S,ION2018=T$ 对$S$建$SAM$,每次都把$T$放进去跑,求出结尾是i的前缀串,能匹配上$S$的最长后缀长度为$f_{i}$ 由于$T$必须在$[l,r ...
- 洛谷P2178 [NOI2015]品酒大会(后缀自动机 线段树)
题意 题目链接 Sol 说一个后缀自动机+线段树的无脑做法 首先建出SAM,然后对parent树进行dp,维护最大次大值,最小次小值 显然一个串能更新答案的区间是\([len_{fa_{x}} + 1 ...
随机推荐
- 【windows】如何让一个程序开机自启动
windows的开机自启动也是将一个程序放在文件夹下即可,将应用程序或者快捷方式放在如下文件夹下,即可实现开机自启动 C:\ProgramData\Microsoft\Windows\Start Me ...
- Qt — 子窗体操作父窗体中的方法
父窗体与子窗体各自的代码如下: 1. 父窗体的代码: void FartherWindow::addactions() { SubWindow subwindow(this); // 把父窗体本身t ...
- tmux基本使用方法
tmux是一款优秀的终端复用软件.tmux采用C/S模型构建,输入tmux命令就相当于开启了一个服务器,此时默认将新建一个会话,然后会话中默认新建一个窗口,窗口中默认新建一个面板. 一个tmux se ...
- python中的编码转换
今天遇到了一个问题,将字符串“\uxxxx\uxxxx”转换成汉字.网上查了很多资料都不行. 后来看到,发现一个函数就OK了. str = str.decode('unicode_escape') 等 ...
- UVA11426 GCD - Extreme (II) —— 欧拉函数
题目链接:https://vjudge.net/problem/UVA-11426 题意: 求 ∑ gcd(i,j),其中 1<=i<j<=n . 题解:1. 欧拉函数的定义:满足 ...
- Appium——元素定位
首先介绍两种定位元素的工具,appium自带的 Inspector 和 android SDK自带的 uiautomatorviewer 1.UIAutomator Viewer比较简单,在模拟器打开 ...
- redis简介及安装
1 redis简介及安装 1.1 Redis是什么 REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. 首 ...
- C++之封装
希望暴露public 希望隐藏private 对象实例化有两种方式,从栈实例化,从堆(new出来的)实例化. 以谁做什么作为核心. public 放前面,private放后面(属性可以定义为priva ...
- 带动画效果的jQuery手风琴
带动画效果的jQuery特效手风琴是一款带动画效果的手风琴作品,非常实用,可以用在新闻列表.FAQ等模块,默认的是打开第一个选项,查看其它的时候直接点击加号按钮就展开. 源码地址:http://www ...
- jsp日期插件My97DatePicker 强大的日期控件 使用方便简单(转)
本文属转载(希望对编程爱好者有所帮助)详情请访问官方网站 http://www.my97.net/dp/index.asp 一. 简介 1. 简介 目前的版本是:4.7 2. 注意事项 My97Dat ...