P4770 [NOI2018]你的名字
蒟蒻表示不会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]你的名字的更多相关文章
- UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)
NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...
- #P4770 [NOI2018]你的名字 的题解
题目背景 实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题. 题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外 ...
- 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]
传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...
- luogu P4770 [NOI2018]你的名字
传送门 upd 19.4.24: WC这个做法真的有问题,不往回跳会WA是因为一开始跳到了S[1...l-1]所对应的点,然后往后接字符的时候可能会因为不在正确的endpos中,然后往回跳过头,其实一 ...
- 洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)
传送门 我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解…… 首先,对$S$和每一次的$T$都建一个SAM 先考虑一下$l=1,r=\left| S \right|$的情况 设$lim_i ...
- BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并
题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...
- 【BZOJ5417】[NOI2018]你的名字(线段树,后缀自动机)
[BZOJ5417][NOI2018]你的名字(线段树,后缀自动机) 题面 BZOJ 洛谷 题解 首先考虑\(l=1,r=|S|\)的做法,对于每次询问的\(T\)串,暴力在\(S\)串的\(SAM\ ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- [NOI2018]你的名字(后缀自动机+线段树)
题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...
随机推荐
- NoHttp封装--03 cookie
NoHttp请求自动维持Cookie: 1.支持Session.Cookie.临时Cookie的位置. 2.支持App重启.关机开机后继续持久化维持. 3.提供了接口,允许开发者监听Coo ...
- Java并发编程(四)synchronized
一.synchronized同步方法或者同步块 在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁. 举个简单的例子:如果对临界资源加上 ...
- recovery log直接输出到串口
我们在调试recovery升级的时候,我们经常需要查看recovery的log,google的原始逻辑中,recovery的log并非直接输出到串口,我们需要输入命令才能获取,我们有三种方式: 第一种 ...
- Python参数传递(传值&传引用)
# 测试参数是传值还是传引用def test(arg): print("test before") print(id(arg)) arg[1]=30 # 测试可变对象 # arg[ ...
- 在Java中调用C/C++本地库
JNI是Java Native Interface的英文缩写, 中文翻译为本地调用, 自从Java 1.1开始就成为了Java标准的一部分. C/C++是系统级的编程语言, 可以用来开发任何和系统相关 ...
- webApi core2 DI通过代码来获取容器里面已注入的对象
请求服务 来自 HttpContext 的一次 ASP.NET 请求中可用的服务通过 RequestServices 集合公开的. 请求服务将你配置的服务和请求描述为应用程序的一部分.当你的对象指定依 ...
- plsql developer如何查询SQL语句执行历史记录(转)
相信很多在plsql developer调试oracle的朋友,经常会遇到在plsql developer执行的某一条SQL语句没有保存,那么我们在plsql developer下如何找到我们执行过的 ...
- 记一次ubuntuyu源码安装node.js
1.官网下载源码 2.下载后解压,在终端打开,并进入文件夹 3../configure 这一步可谓是一波三折,先是没有Python 安装Python 又说没有G++,安装G++后终于成功执行./con ...
- shell基本语法记录
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个 ...
- CorelDRAW(cdr) 2018安装教程详解
令人期待的coreldraw2018最新版已经发布了,相信各位设计从业者已经迫不及待想要知道这次版本会带来什么新的东西,本次小编带来的coreldraw2018破解版,附有注册补丁,激活之后可以永久免 ...