蒟蒻表示不会sam凉凉了,所以只能提高SA技巧?

题意:有一个串\(A\),每次选择一个\(A\)的子串\(A'\),以及串\(B\),问\(B\)的所有本质不同子串中不在\(A'\)中的串的数量。

(定义\(A_i\)表示以字符\(A_i\)开头的后缀,\(B_i\)同理)

\(B\)的本质不同字串显然是\(|B|*(|B|+1)/2\)了,然后要减去本质不同的在\(A'\)中的串

首先把所有串拼一起,把SA建出来,辣么就可以在SA中找到\(A'\)所对应的所有后缀,对于\(B\)对应的每个后缀\(B_i\),计算\(\max_{j=l}^r LCP(A_j,B_i)\),就是在\(A'\)中的前缀数量,加起来就是这个东西了,由于还要判重,所以计入的其实是\(\max(0,\max_{j=l}^r LCP(A_j,B_i)-LCP(B_i,next(B_{i})))\),其中\(next(B_i)\)意思是在\(B\)串的SA上\(B_i\)的后继

\(LCP(B_i,next(B_{i}))\)随便算是吧,现在要算的是\(\max_{j=l}^rLCP(A_j,B_i)\)

要算LCP的max值,显然只要在SA上求出前驱和后继计算就行了,那么要算区间前驱后继,就是二逼平衡树了

#include<bits/stdc++.h>
#define il inline
#define vd void
typedef long long ll;
#define Log(x) (31-__builtin_clz(x))
il ll gi(){
ll x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return x*f;
}
char S[1600037];
int t[100010],lent[100010];
int n,N;
int ql[100010],qr[100010];
namespace SA{
int x[1600037],y[1600037],_[1600037],SA[1600037],rk[1600037],ht[1600037],t[1600037];
int st[21][1600037];
il int LCP(int x,int y){
if(!x||!y)return 0;
if(x==y)return 1e9;
int l=Log(y-x);
return std::min(st[l][x],st[l][y-(1<<l)]);
}
il int gety(int x){return x<=N?y[x]:-1;}
il vd getSA(){
int set=128;
for(int i=1;i<=N;++i)++t[x[i]=S[i]];
for(int i=1;i<=set;++i)t[i]+=t[i-1];
for(int i=N;i;--i)SA[t[x[i]]--]=i;
for(int k=1;k<=N;k<<=1){
int p=0;
for(int i=N-k+1;i<=N;++i)y[++p]=i;
for(int i=1;i<=N;++i)if(SA[i]>k)y[++p]=SA[i]-k;
for(int i=0;i<=set;++i)t[i]=0;
for(int i=1;i<=N;++i)++t[x[y[i]]];
for(int i=1;i<=set;++i)t[i]+=t[i-1];
for(int i=N;i;--i)SA[t[x[y[i]]]--]=y[i];
memcpy(_,x,sizeof _);
memcpy(x,y,sizeof _);
memcpy(y,_,sizeof _);
x[SA[1]]=p=1;
for(int i=2;i<=N;++i){
if(gety(SA[i])!=gety(SA[i-1])||gety(SA[i]+k)!=gety(SA[i-1]+k))++p;
x[SA[i]]=p;
}
if(p>=N)break;set=p;
}
for(int i=1;i<=N;++i)rk[SA[i]]=i;
for(int i=1,j,k=0;i<=N;++i){
if(rk[i]==N)continue;
if(k)--k;
j=SA[rk[i]+1];
while(S[i+k]==S[j+k])++k;
ht[rk[i]]=k;
}
for(int i=1;i<=N;++i)st[0][i]=ht[i];
for(int i=1;i<=Log(N);++i)
for(int j=1;j+(1<<i)-1<=N;++j)
st[i][j]=std::min(st[i-1][j],st[i-1][j+(1<<i-1)]);
//for(int i=1;i<=N;++i)printf("%d %s\n",ht[i],S+SA[i]);
}
}
#define mid ((l+r)>>1)
int rt[500010],ls[20000010],rs[20000010],sum[20000010],cnt;
il vd build(int&x,int l,int r){
x=++cnt;if(l==r)return;
build(ls[x],l,mid),build(rs[x],mid+1,r);
}
il vd update(int&x,int l,int r,const int&p){
++cnt;ls[cnt]=ls[x],rs[cnt]=rs[x],sum[cnt]=sum[x];x=cnt;
++sum[x];if(l==r)return;
if(p<=mid)update(ls[x],l,mid,p);
else update(rs[x],mid+1,r,p);
}
int pp;
il int query_nxt(int x,int y,int l,int r){
if(!(sum[y]-sum[x]))return 0;
if(l==r)return l;
if(mid<pp)return query_nxt(rs[x],rs[y],mid+1,r);
if(pp<l){
if(sum[ls[y]]-sum[ls[x]])return query_nxt(ls[x],ls[y],l,mid);
else return query_nxt(rs[x],rs[y],mid+1,r);
}else{
int t=query_nxt(ls[x],ls[y],l,mid);
return t?t:query_nxt(rs[x],rs[y],mid+1,r);
}
}
il int query_pre(int x,int y,int l,int r){
if(!(sum[y]-sum[x]))return 0;
if(l==r)return l;
if(pp<=mid)return query_pre(ls[x],ls[y],l,mid);
if(r<pp){
if(sum[rs[y]]-sum[rs[x]])return query_pre(rs[x],rs[y],mid+1,r);
else return query_pre(ls[x],ls[y],l,mid);
}else{
int t=query_pre(rs[x],rs[y],mid+1,r);
return t?t:query_pre(ls[x],ls[y],l,mid);
}
}
#undef mid
il bool check(int l,int r,int p,int k){
if(l>r)return 0;
pp=p;
if(SA::LCP(query_pre(rt[l-1],rt[r],1,N),p)>=k)return 1;
if(SA::LCP(p,query_nxt(rt[l-1],rt[r],1,N))>=k)return 1;
return 0;
}
int lcp[1600037];
int main(){
#ifdef XZZSB
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
scanf("%s",S+1);n=strlen(S+1);N=n+1;
int Q=gi();t[1]=n+1;S[n+1]='~';
int sumt=0;
for(int i=1;i<=Q;++i){
scanf("%s",S+t[i]+1);ql[i]=gi(),qr[i]=gi();
lent[i]=strlen(S+t[i]+1);t[i+1]=t[i]+lent[i]+1;N+=lent[i]+1;
sumt+=lent[i];
S[t[i]+lent[i]+1]='|';
}
SA::getSA();
build(rt[0],1,N);
for(int i=1;i<=n;++i)rt[i]=rt[i-1],update(rt[i],1,N,SA::rk[i]);
int l,r;
for(int o=1;o<=Q;++o){
l=ql[o],r=qr[o];
std::vector<int>sufs;
for(int i=t[o]+1;i<=t[o]+lent[o];++i)sufs.push_back(SA::rk[i]);
std::sort(sufs.begin(),sufs.end());
ll res=1ll*lent[o]*(lent[o]+1)/2;
for(int i=1;i<sufs.size();++i)res-=(lcp[SA::SA[sufs[i-1]]]=SA::LCP(sufs[i-1],sufs[i]));
for(int i=t[o]+1,j=0;i<=t[o]+lent[o];++i){
if(j)--j;
while(check(l,r-j,SA::rk[i],j+1))++j;
res-=std::max(0,j-lcp[i]);
}
printf("%lld\n",res);
}
return 0;
}

P4770 [NOI2018]你的名字的更多相关文章

  1. UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)

    NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...

  2. #P4770 [NOI2018]你的名字 的题解

    题目背景 实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题. 题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外 ...

  3. 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]

    传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...

  4. luogu P4770 [NOI2018]你的名字

    传送门 upd 19.4.24: WC这个做法真的有问题,不往回跳会WA是因为一开始跳到了S[1...l-1]所对应的点,然后往后接字符的时候可能会因为不在正确的endpos中,然后往回跳过头,其实一 ...

  5. 洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)

    传送门 我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解…… 首先,对$S$和每一次的$T$都建一个SAM 先考虑一下$l=1,r=\left| S \right|$的情况 设$lim_i ...

  6. BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并

    题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...

  7. 【BZOJ5417】[NOI2018]你的名字(线段树,后缀自动机)

    [BZOJ5417][NOI2018]你的名字(线段树,后缀自动机) 题面 BZOJ 洛谷 题解 首先考虑\(l=1,r=|S|\)的做法,对于每次询问的\(T\)串,暴力在\(S\)串的\(SAM\ ...

  8. bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)

    bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...

  9. [NOI2018]你的名字(后缀自动机+线段树)

    题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...

随机推荐

  1. Sqlserver精简安装选项

  2. 使用spark DStream的foreachRDD时要注意哪些坑?

    答案: 两个坑, 性能坑和线程坑 DStream是抽象类,它把连续的数据流拆成很多的小RDD数据块, 这叫做“微批次”, spark的流式处理, 都是“微批次处理”. DStream内部实现上有批次处 ...

  3. Centos7安装搭建Bugzilla 5.0

    1.安装准备: Centos7保证网络连通,如果网络不能连通,可通过配置yum源使用代理服务. vim /etc/yum.conf # The proxy server - proxy server: ...

  4. MATLAB矩阵的LU分解及在解线性方程组中的应用

    作者:凯鲁嘎吉 - 博客园http://www.cnblogs.com/kailugaji/ 三.实验程序 五.解答(按如下顺序提交电子版) 1.(程序) (1)LU分解源程序: function [ ...

  5. 软件工程实践_Task2_sudoku

    软工实践_Task2 标签(空格分隔): 软工实践 相关要求:第二次作业--个人项目实战 github:传送门 解题思路 先是一点杂谈. 首先,看完作业要求之后,心里先有个大概的框架. 语言:C++ ...

  6. 【BZOJ3622】已经没有什么好害怕的了

    Description 已经使 Modoka 有签订契约, 和自己一起战斗的想法后 , Mami 忽然感到自己不再是孤单一人了呢. 于是, 之前的谨慎的战斗作风也消失了 , 在对 Charlotte ...

  7. C#事件の事件处解

    C# 事件(Event) 事件(Event) 基本上说是一个用户操作,如按键.点击.鼠标移动等等,或者是一些出现,如系统生成的通知.应用程序需要在事件发生时响应事件.例如,中断.事件是用于进程间通信. ...

  8. 转://Oracle 11gR2 RAC ASM磁盘全部丢失后的恢复

    一.环境描述 (1)Oracle 11.2.0.3 RAC ON Oracle Linux 6 x86_64,只有一个ASM外部冗余磁盘组--DATA: (2)OCR,VOTEDISK,DATAFIL ...

  9. redis php扩展及基本命令

    linux 安装php mysql redis memchache 等工具 用 OneinStack 安装步骤 注意 如果有单独数据盘,建议您先挂载数据盘,建议将网站内容.数据库放在数据盘中.如何挂载 ...

  10. PHP开发小技巧③—实现多维数组转化为一维数组

    在平常的项目开发中我们多会用到让多维数组转化为一维数组的情况,但是很多Programmer不会将其进行转化,也有些没有想到很好的算法然后经过乱起八糟的运算方式将其勉强转化好,但是所写的程序代码冗余非常 ...