Luogu4770 NOI2018你的名字(后缀数组+线段树)
即求b串有多少个本质不同的非空子串,在a串的给定区间内未出现。即使已经8102年并且马上就9102年了,还是要高举SA伟大旗帜不动摇。
考虑离线,将所有询问串及一开始给的串加分隔符连起来,求出SA。对于每个询问,我们对串的每个后缀,求出其在给定区间中最长的lcp是多少。这样就能得到不考虑本质不同时的答案,再考虑该串名次数组中相邻后缀的lcp去一下重即可。
考虑怎么求这个最长的lcp。设该后缀在名次数组中的位置是i,给定区间是[l,r],这个东西实际上就是max{min{lcp(i..j),r-saj+1}} (saj>=l)。容易发现取min的两个函数实际上一个单减一个单增,于是容易想到的做法是二分答案,找到名次数组上对应区间,查询区间内是否有合法的sa值。但这样就是log^2了。实际上按sa倒序建可持久化线段树(或者仍然离线就只需要线段树了),线段树上二分查询即可。这个二分需要自底向上再自顶向下以保证复杂度,写起来感觉非常恶心。
非常神奇的是这份代码在uoj过掉了,在luogu被卡常,在lojT成0分。
upd:更神奇的是深夜在luogu交了一次就过掉了,而本来只有64。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 1600010
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<''||c>'')) c=getchar();return c;}
int gcd(int n,int m){return m==?n:gcd(m,n%m);}
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
char s[N];
int len,n,m,a[N],sa[N],sa2[N],rk[N<<],tmp[N<<],cnt[N],h[N],f[][N],lg2[N],lcp[N],id[N];
ll ans[N];
struct data
{
int s,t,l,r,i;
bool operator <(const data&a) const
{
return l>a.l;
}
}q[N];
struct data2{int l,r,x;
}tree[N<<];
void make(int m)
{
for (int i=;i<=n;i++) cnt[rk[i]=a[i]]++;
for (int i=;i<=m;i++) cnt[i]+=cnt[i-];
for (int i=n;i>=;i--) sa[cnt[rk[i]]--]=i;
for (int k=;k<=n;k<<=)
{
int p=;
for (int i=n-k+;i<=n;i++) sa2[++p]=i;
for (int i=;i<=n;i++) if (sa[i]>k) sa2[++p]=sa[i]-k;
memset(cnt,,m+<<);
for (int i=;i<=n;i++) cnt[rk[i]]++;
for (int i=;i<=m;i++) cnt[i]+=cnt[i-];
for (int i=n;i>=;i--) sa[cnt[rk[sa2[i]]]--]=sa2[i];
memcpy(tmp,rk,sizeof(tmp));
p=;rk[sa[]]=;
for (int i=;i<=n;i++)
{
if (tmp[sa[i-]]!=tmp[sa[i]]||tmp[sa[i-]+k]!=tmp[sa[i]+k]) p++;
rk[sa[i]]=p;
}
m=p;if (m==n) break;
}
for (int i=;i<=n;i++)
{
h[i]=max(h[i-]-,);
while (a[i+h[i]]==a[sa[rk[i]-]+h[i]]) h[i]++;
}
for (int i=;i<=n;i++) f[][i]=h[sa[i]];
for (int i=;i<=n;i++)
{
lg2[i]=lg2[i-];
if ((<<lg2[i])<=i) lg2[i]++;
}
for (int j=;j<;j++)
for (int i=;i<=n;i++)
f[j][i]=min(f[j-][i],f[j-][min(n,i+(<<j-))]);
}
int query(int x,int y)
{
if (x==y) return N;x++;
return min(f[lg2[y-x+]][x],f[lg2[y-x+]][y-(<<lg2[y-x+])+]);
}
void build(int k,int l,int r)
{
tree[k].l=l,tree[k].r=r;tree[k].x=N;
if (l==r) {id[l]=k;return;}
int mid=l+r>>;
build(k<<,l,mid);
build(k<<|,mid+,r);
}
int findl(int x,int r)
{
int ans=,k=id[x],c=tree[k].x;
while (k!=)
if (!(k&)) k>>=;
else if (r-min(c,tree[k-].x)+>=query(tree[k-].l,x)) {k>>=;break;}
else c=min(c,tree[k-].x),k>>=,ans=max(ans,min(query(tree[k].l,x),r-c+));
while (tree[k].l<tree[k].r&&tree[k].r>=x) k<<=;
while (tree[k].l<tree[k].r)
if (r-min(c,tree[k<<|].x)+>=query(tree[k<<|].l,x)) k=k<<|;
else k<<=,c=min(c,tree[k+].x),ans=max(ans,min(query(tree[k+].l,x),r-c+));
ans=max(ans,min(query(tree[k].l,x),r-min(tree[k].x,c)+));
return ans;
}
int findr(int x,int r)
{
int ans=,k=id[x],c=tree[k].x;
while (k!=)
if (k&) k>>=;
else if (r-min(c,tree[k+].x)+>=query(x,tree[k+].r)) {k>>=;break;}
else c=min(c,tree[k+].x),k>>=,ans=max(ans,min(query(x,tree[k].r),r-c+));
while (tree[k].l<tree[k].r&&tree[k].l<=x) k=k<<|;
while (tree[k].l<tree[k].r)
if (r-min(c,tree[k<<].x)+>=query(x,tree[k<<].r)) k<<=;
else k=k<<|,c=min(c,tree[k-].x),ans=max(ans,min(query(x,tree[k-].r),r-c+));
ans=max(ans,min(query(x,tree[k].r),r-min(tree[k].x,c)+));
return ans;
}
bool cmp(const int&a,const int&b)
{
return rk[a]<rk[b];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("name.in","r",stdin);
freopen("name.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
scanf("%s",s+);len=n=strlen(s+);
for (int i=;i<=n;i++) a[i]=s[i]-'a'+;
a[++n]=;
m=read();
for (int i=;i<=m;i++)
{
scanf("%s",s+);int t=strlen(s+);
q[i].s=n+;
for (int j=;j<=t;j++) a[++n]=s[j]-'a'+;
q[i].t=n;a[++n]=;
q[i].l=read(),q[i].r=read(),q[i].i=i;
}
sort(q+,q+m+);
make();
build(,,n);
q[].l=len+;
for (int i=;i<=m;i++)
{
for (int j=q[i-].l-;j>=q[i].l;j--)
for (int k=id[rk[j]];k;k>>=) tree[k].x=j;
for (int j=q[i].s;j<=q[i].t;j++) tmp[j]=j;
sort(tmp+q[i].s,tmp+q[i].t+,cmp);
for (int j=q[i].s;j<=q[i].t;j++) lcp[j]=max(findl(rk[tmp[j]],q[i].r),findr(rk[tmp[j]],q[i].r));
ans[q[i].i]=q[i].t-tmp[q[i].s]+-lcp[q[i].s];
for (int j=q[i].s+;j<=q[i].t;j++)
ans[q[i].i]+=q[i].t-tmp[j]+-max(lcp[j],query(min(rk[tmp[j]],rk[tmp[j-]]),max(rk[tmp[j]],rk[tmp[j-]])));
}
for (int i=;i<=m;i++) printf(LL,ans[i]);
return ;
}
Luogu4770 NOI2018你的名字(后缀数组+线段树)的更多相关文章
- luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并
其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(T\) ...
- 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$个字 ...
- [NOI2018]你的名字(后缀自动机+线段树)
题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...
- BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)
LOJ 洛谷 BZOJ 考虑\(l=1,r=|S|\)的情况: 对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\). 因为要去重,对\(T\ ...
- BZOJ 1396: 识别子串( 后缀数组 + 线段树 )
这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heigh ...
- 【XSY1551】往事 广义后缀数组 线段树合并
题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\) ...
- BZOJ 2865 字符串识别 | 后缀数组 线段树
集训讲字符串的时候我唯一想出正解的题-- 链接 BZOJ 2865 题面 给出一个长度为n (n <= 5e5) 的字符串,对于每一位,求包含该位的.最短的.在原串中只出现过一次的子串. 题解 ...
- bzoj 1396: 识别子串 && bzoj 2865: 字符串识别【后缀数组+线段树】
根据height数组的定义,和当前后缀串i最长的相同串的长度就是max(height[i],height[i+1]),这个后缀贡献的最短不同串长度就是len=max(height[i],height[ ...
随机推荐
- Android DatePickerDialog使用案例
DatePickerDialog提供了一个弹出的Dialog供用户选择日期. 在这里分享一下其使用方法,效果图如下: DatePickerActivity.java package com.yw.my ...
- AbelSu教你搭建go语言开发环境
go语言官网:https://golang.org/ windows:官网下载go1.6.windows-amd64.msi安装文件,安装位置选择默认C:\Go\安装结束后配置环境变量Path: C: ...
- Python 调用 Redis API
安装Redis包 在\Python27\Scripts目录下执行 pip install redis Python操作Redis __author__ = 'Edward' import redis ...
- Swoole Task 的应用
目录 概述 代码 小结 扩展 参考文档 概述 Swoole 异步Task,主要实现调用异步任务的执行. 常用的场景:异步支付处理.异步订单处理.异步日志处理.异步发送邮件/短信等. Swoole 的实 ...
- Swoole Timer 的应用
目录 你好,Swoole Timer 应用场景 参考文档 你好,Swoole PHP 的协程高性能网络通信引擎,使用 C/C++ 语言编写,提供了多种通信协议的网络服务器和客户端模块. Swoole ...
- # 20155319 Exp3 免杀原理与实践
20155319 Exp3 免杀原理与实践 基础问题 (1)杀软是如何检测出恶意代码的? 基于特征码的检测 启发式的恶意软件检测 基于行为的恶意软件检测 (2)免杀是做什么? 免杀,从字面进行理解,避 ...
- 20155334 曹翔 Exp3 免杀原理与实践
20155334 曹翔 Exp3 免杀原理与实践 小记:这次实验,困难重重,失败练练,搞得我们是心急如焚,焦头烂额,哭爹喊娘 一.基础问题回答 杀软是如何检测出恶意代码的? 每个杀软都有自己的检测库, ...
- python 23 种 设计模式
频率 所属类型 模式名称 模式 简单定义 5 创建型 Singleton 单件 保证一个类只有一个实例,并提供一个访问它的全局访问点. 4 创建型 Abstract Factory 抽象工厂 提供一个 ...
- Number.toString SyntaxError
问题分析 问题 20.toString(); VM163:1 Uncaught SyntaxError: Invalid or unexpected token 分析 "."号的原 ...
- Security4:授予查看定义,执行SP和只读数据的权限
SQL Server数据库有完善的权限管理机制,对于存储过程,其权限分为查看定义,执行和修改,查看SP定义的权限是:VIEW DEFINITION ,执行存储过程的权限是:EXECUTE,修改SP的权 ...