题目:https://loj.ac/problem/2720

自己总是分不清 “SAM上一个点的 len[ ] ” 和 “一个串的前缀在 SAM 上匹配的 len ”。

于是原本想的 68 分做法是,求出 T 的本质不同子串个数,减去 T 在 S 的 SAM 上走的 fail 树的链并权值。SAM 上一个点的权值就是它代表的子串个数(len[ cr ] - len[ fa ])。

其实不行。因为 T 走到 S 的 SAM 的某个点,不是能匹配该点代表的所有子串,而是只作为 T 的一个前缀匹配了一个最长的 S 的子串。

考虑求 T 的本质不同子串,通过给 T 建 SAM 来求。 SAM 的每个点表示 T 的一个(一些位置)前缀的一些后缀。

T 的 SAM 的每个点给答案的贡献不是 len[ cr ] - len[ fa ] ,要减去与 S 重合的部分。

所以就是想知道 T 的每个前缀的后缀能与 S 的子串匹配的最大长度。 T 在 S 的 SAM 上走一遍即可。

然后 T 的 SAM 上每个点找一个它代表的子串们可以对应的 T 的前缀,已知该前缀的最长后缀匹配长度是 l2[ i ] ,那么 SAM 该点的贡献就是 max( 0 , len[ cr ] - max( len[ fa ] , l2[ i ] ) ) 。

如果是 S 的一个区间的子串,就让 T 在 S 的 SAM 上走的时候,如果不能匹配在这个区间里,就一直跳 fa 。

用主席树求出 S 的 SAM 每个点的 right 集合具体是什么,当前 T 走着,想匹配 tlen+1 长度,就是看看 [ ql + tlen -1 , qr ] 里有没有元素。

注意不是一旦失败就跳 fa ,而是要在当前节点一直 tlen -- ;如果 tlen 减得和 len[ fa ] 一样,才跳 fa 。

一直 -- ,复杂度是对的。因为 T 走一步,tlen 最多加1; 如果减成0,就会退出,所以 tlen 的移动长度最多 \( 2*\sum |T| \) 。

写那个 tlen -- 的 while 循环的时候要注意一些。

注意每次给 T 建自动机之前要清空上次的数组。而且是清空 2*len 那么多。

注意线段树合并,新建的节点要继承原来的 ls 、 rs 之类的信息!

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls Ls[cr]
#define rs Rs[cr]
#define ll long long
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
int Mx(int a,int b){return a>b?a:b;}
int Mn(int a,int b){return a<b?a:b;}
const int N=1e6+,M=4e7+,K=;
int n,lst=,cnt=,len[N],fa[N],go[N][K],tx[N],q[N];
int ql,qr,l2[N],tot,rt[N],Ls[M],Rs[M],sm[M];
char s[N];
namespace S1{
int lst,cnt,len[N],fa[N],go[N][K],dy[N];
void init(int n)
{
lst=cnt=;
for(int i=,lm=n*;i<=lm;i++)//////n*2!!!!!
memset(go[i],,sizeof go[i]);
}
int Ins(int w,int bh)
{
int p=lst,np=++cnt; lst=np;len[np]=len[p]+;dy[np]=bh;
for(;p&&!go[p][w];p=fa[p])go[p][w]=np;
if(!p){fa[np]=;return np;}
int q=go[p][w];
if(len[q]==len[p]+){fa[np]=q;return np;}
int nq=++cnt; len[nq]=len[p]+;dy[nq]=dy[q];//
fa[nq]=fa[q]; fa[q]=nq; fa[np]=nq;
memcpy(go[nq],go[q],sizeof go[q]);
for(;go[p][w]==q;p=fa[p])go[p][w]=nq;
return np;
}
void solve()
{
int m=strlen(s+); init(m);/////
for(int i=;i<=m;i++)Ins(s[i]-'a',i);
ll ans=;
for(int i=;i<=cnt;i++)
ans+=Mx(,len[i]-Mx(len[fa[i]],l2[dy[i]]));
printf("%lld\n",ans);
}
}
int Ins(int w)
{
int p=lst,np=++cnt; lst=np;len[np]=len[p]+;
for(;p&&!go[p][w];p=fa[p])go[p][w]=np;
if(!p){fa[np]=;return np;}
int q=go[p][w];
if(len[q]==len[p]+){fa[np]=q;return np;}
int nq=++cnt; len[nq]=len[p]+;
fa[nq]=fa[q]; fa[q]=nq; fa[np]=nq;
memcpy(go[nq],go[q],sizeof go[q]);
for(;go[p][w]==q;p=fa[p])go[p][w]=nq;
return np;
}
void Rsort()
{
for(int i=;i<=cnt;i++)tx[len[i]]++;
for(int i=;i<=n;i++)tx[i]+=tx[i-];
for(int i=;i<=cnt;i++)q[tx[len[i]]--]=i;
}
int nwnd(int pr)
{
int cr=++tot; ls=Ls[pr];rs=Rs[pr];
sm[cr]=sm[pr]; return cr;
}
void build(int l,int r,int &cr,int p)
{
cr=++tot; sm[cr]=;
if(l==r)return; int mid=l+r>>;
if(p<=mid)build(l,mid,ls,p);
else build(mid+,r,rs,p);
}
void mrg(int l,int r,int &cr,int pr)
{
if(!cr||!pr){cr=nwnd(cr|pr); return;}
cr=nwnd(cr); int mid=l+r>>;//not cr=++tot!!!
mrg(l,mid,ls,Ls[pr]); mrg(mid+,r,rs,Rs[pr]);
sm[cr]=sm[ls]+sm[rs];
}
int qry(int l,int r,int cr,int L,int R)
{
if(L>R)return ;
if(l>=L&&r<=R)return sm[cr];
int mid=l+r>>;
if(L>mid)return qry(mid+,r,rs,L,R);
if(R<=mid)return qry(l,mid,ls,L,R);
return qry(l,mid,ls,L,R)+qry(mid+,r,rs,L,R);
}
bool chk(int cr,int tlen)
{ return qry(,n,rt[cr],ql+tlen-,qr);}
int main()
{
freopen("name.in","r",stdin);
freopen("name.out","w",stdout);
scanf("%s",s+); n=strlen(s+);
for(int i=;i<=n;i++)
{
int d=Ins(s[i]-'a');
build(,n,rt[d],i);
}
Rsort();
for(int i=cnt;i>;i--)
mrg(,n,rt[fa[q[i]]],rt[q[i]]);
int Q=rdn();
while(Q--)
{
scanf("%s",s+); ql=rdn();qr=rdn();
int cr=,m=strlen(s+),tlen=;
for(int i=;i<=m;i++)
{
int w=s[i]-'a';
while()
{
if(go[cr][w]&&chk(go[cr][w],tlen+))
{ tlen++; cr=go[cr][w]; break;}
if(!tlen)break;
if(!go[cr][w])
{cr=fa[cr];tlen=len[cr];continue;}
tlen--; if(tlen==len[fa[cr]])cr=fa[cr];
}
l2[i]=tlen;
}
S1::solve();
}
return ;
}

LOJ 2720 「NOI2018」你的名字——后缀自动机的更多相关文章

  1. loj#2720. 「NOI2018」你的名字

    链接大合集: loj uoj luogu bzoj 单纯地纪念一下写的第一份5K代码.../躺尸 因为ZJOI都不会所以只好写NOI的题了... 总之字符串题肯定一上来就拼个大字符串跑后缀数组啦! ( ...

  2. LOJ_#2720. 「NOI2018」你的名字 _后缀数组+主席树+倍增

    题面: https://loj.ac/problem/2720 考虑枚举T串的每个后缀i,我们要做两件事. 一.统计有多少子串[i,j]在S中要求位置出现. 二.去重. 第二步好做,相当于在后缀数组上 ...

  3. 【LOJ】#2720. 「NOI2018」你的名字

    题解 把S串建一个后缀自动机 用一个可持久化权值线段树维护每个节点的right集合是哪些节点 求本质不同的子串我们就是要求T串中以每个点为结束点的串有多少在\(S[l..r]\)中出现过 首先我们需要 ...

  4. 「NOI2018」你的名字

    「NOI2018」你的名字 题目描述 小A 被选为了\(ION2018\) 的出题人,他精心准备了一道质量十分高的题目,且已经 把除了题目命名以外的工作都做好了. 由于\(ION\) 已经举办了很多届 ...

  5. LOJ #2721. 「NOI2018」屠龙勇士(set + exgcd)

    题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...

  6. loj#2718. 「NOI2018」归程

    题目链接 loj#2718. 「NOI2018」归程 题解 按照高度做克鲁斯卡尔重构树 那么对于询问倍增找到当前点能到达的高度最小可行点,该点的子树就是能到达的联通快,维护子树中到1节点的最短距离 s ...

  7. loj#2721. 「NOI2018」屠龙勇士

    题目链接 loj#2721. 「NOI2018」屠龙勇士 题解 首先可以列出线性方程组 方程组转化为在模p意义下的同余方程 因为不保证pp 互素,考虑扩展中国剩余定理合并 方程组是带系数的,我们要做的 ...

  8. Loj #2719. 「NOI2018」冒泡排序

    Loj #2719. 「NOI2018」冒泡排序 题目描述 最近,小 S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 *\(1\) 到 \(n\) 的排列*的冒泡排序. 下面是对冒泡排 ...

  9. loj 2719 「NOI2018」冒泡排序 - 组合数学

    题目传送门 传送门 题目大意 (相信大家都知道) 显然要考虑一个排列$p$合法的充要条件. 考虑这样一个构造$p$的过程.设排列$p^{-1}_{i}$满足$p_{p^{-1}_i} = i$. 初始 ...

随机推荐

  1. JS中关于引用类型数据及函数的参数传递

    (JavaScript 中,函数的参数传递方式都是按值传递,没有按引用传递的参数) 一.数据类型 在 javascript 中数据类型可以分为两类: 基本类型值 primitive type,比如Un ...

  2. HDU 6073 Matching In Multiplication —— 2017 Multi-University Training 4

    Matching In Multiplication Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K ( ...

  3. 回复git@vger.kernel.org的注意事项

    比如回复这封邮件 https://public-inbox.org/git/db2dcf54-8b1c-39b1-579c-425ef158c6a1@kdbg.org/ Reply instructi ...

  4. Protocol协议分发器

    1. 用途: 能够制定多个对象实现<Protocol>, 同一个代理方法,可以在多个对象中同时实现 2.原理: 利用消息转发机制,将方法分发到多个对象中 使用方式: self.tableV ...

  5. linux-centOS环境下安装jdk8

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/lx_Frolf/article/det ...

  6. 服务端:WCF服务层安全检查核心类

    using System.Data; using CSFrameworkV4_5.Common; using CSFrameworkV4_5.Core.SystemSecurity; using CS ...

  7. 爱奇艺面试Python,竟然挂在第5轮…(转)

    今天给大家分享我曾经在爱奇艺的面试,过程还是比较有意思的,可以给大家一些参考 聊骚阶段 嗲妹妹:你好,我是爱奇艺的HR,我们正在招聘运维开发岗位,请问您最近有在看工作机会吗? 我:(这声音也太酥了吧我 ...

  8. Linux(Ubuntu)常用命令(一)

    Linux先知: Linux历史: 关于这个我就不再多说了,其实是一个很有意思的故事串,网上找下一大堆. 类Unix系统目录结构: ubuntu没有盘符这个概念,只有一个根目录/,所有文件都在它下面 ...

  9. appium常见问题03_appium脚本报错selenium.common.exceptions.WebDriverException

    运行appium脚本时报错selenium.common.exceptions.WebDriverException...,如下截图: 该报错说明appium和app的内置chrome版本不一致 [解 ...

  10. python基础--冒泡排序

    1.冒泡排序 1.首先用一张图来形象描述一下冒泡排序: 2.废话不多说,直接上代码 # 1.导入随机模块 import random # 2.定义一个列表,列表内的元素为20个100以内的随机整数 l ...