loj#2720. 「NOI2018」你的名字
链接大合集:
单纯地纪念一下写的第一份5K代码。。。/躺尸
因为ZJOI都不会所以只好写NOI的题了。。。
总之字符串题肯定一上来就拼个大字符串跑后缀数组啦!
(为了便于说明,放一下样例的sa)
#sbape#sgepe
#sgepe
#smape#sbape#sgepe
amgepe#smape#sbape#sgepe
ape#sbape#sgepe
ape#sgepe
bamgepe#smape#sbape#sgepe
bape#sgepe
cbamgepe#smape#sbape#sgepe
e
e#sbape#sgepe
e#sgepe
e#smape#sbape#sgepe
epe
epe#smape#sbape#sgepe
gepe
gepe#smape#sbape#sgepe
mape#sbape#sgepe
mgepe#smape#sbape#sgepe
pe
pe#sbape#sgepe
pe#sgepe
pe#smape#sbape#sgepe
sbape#sgepe
scbamgepe#smape#sbape#sgepe
sgepe
smape#sbape#sgepe
首先,对于每个T,考虑求出其子串总数。对于T每个后缀[i...|T|],用st表求该后缀和它前面第一个T的后缀的lcp,则这个后缀的有效子串即为[i...j](j属于[i+lcp,|T|])。
(对于"sgepe"的后缀"epe",lcp(10,14)=1,因此,其有效子串为"ep","epe")
然后,我们再考虑去掉其有效子串中S[l...r]中的部分。
考虑64pts的部分分,即l=1,r=n。对于T的后缀[i...|T|],只要求出L=max{lcp( T[i...|T|] , S[j...|S|] )},我们就能算出每个后缀对答案的贡献。可以发现,这两个部分分别在区间上取min和max,即分别递增、递减,因此我们只要找到两个函数值最靠近的位置即可。
考虑100pts,我们可以离线做。按询问的l从大到小排序,每次在线段树上加入新的字符串使得线段树中的字符串为S[i...|S|](i>=l)。显然,i>r的字符串并不会对答案产生影响,于是我们就可以愉快地线段树上二分啦!(二分需要找到最近的满足递减函数小于递增函数的位置,在线段树上需要先上去再下来,总之写起来很恶心就对了。。。写了个人不人鬼不鬼的zkw线段树(好吧其实我并不怎么会zkw线段树。。。)总之这就是长达5k的原因。。。)
(啊,代码真丑。)
(不知道是不是因为大家都是sam而我是sa所以这么慢。。。)
(sa:所以自己常数大反而怪我咯?←_←)
#include <bits/stdc++.h>
using namespace std; #define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define gc getchar()
#define pc putchar typedef pair<int,int> pii;
typedef long long ll; const int N=2e6+; int rd(){
int x=;char c=gc;
while(c<''||c>'') c=gc;
while(c>=''&&c<=''){
x=x*+c-'';
c=gc;
} return x;
} void prt(ll x){
if(x>) prt(x/);
pc(x%+'');
} //Q 查询字符串 l-r/r-l
int L,n,m,sa[N];
char s[N]; namespace Sa{
const int V=;
int ht[N],ar[][N],*rk,*tp,ct[N],mx,ln,st[V+][N],lg[N]; void get_sa(){
rk=ar[];tp=ar[]; rep(i,,n) ct[s[i]]++;
rep(i,,) ct[i]+=ct[i-];
rep(i,,n) sa[ct[s[i]]--]=i;
rep(i,,n){
if(s[sa[i]]!=s[sa[i-]]) ++mx;
rk[sa[i]]=mx;
} for(int k=;mx<n;k<<=){
ln=;
rep(i,n-k+,n) tp[++ln]=i;
rep(i,,n) if(sa[i]>k) tp[++ln]=sa[i]-k; rep(i,,mx) ct[i]=;
rep(i,,n) ct[rk[i]]++;
rep(i,,mx) ct[i]+=ct[i-];
per(i,n,) sa[ct[rk[tp[i]]]--]=tp[i]; swap(tp,rk);
mx=;
rep(i,,n){
int I=sa[i],J=sa[i-];
if(tp[I]!=tp[J]||tp[min(I+k,n+)]!=tp[min(J+k,n+)]) ++mx;
rk[I]=mx;
}
}
} void get_ht(){
rep(i,,n) lg[i]=lg[i>>]+; rep(i,,n){//ht[rk[i]]
if(rk[i]==) continue;
int v=ht[rk[i-]];if(v>) v--;
int J=sa[rk[i]-];
while(s[i+v]==s[J+v]) v++;
ht[rk[i]]=v;
} rep(i,,n) st[][i]=ht[i];
rep(j,,V) rep(i,,n)
st[j][i]=min(st[j-][i],st[j-][min(i+(<<(j-)),n)]);
} void init(){
get_sa();
get_ht();
} int Q(int l,int r){
if(l>r) swap(l,r);
if(l==r) return L+;
if(r>n) return ;
l++;
int j=lg[r-l+];
return min(st[j][l],st[j][r-(<<j)+]);
}
}
using Sa::Q;
using Sa::rk; namespace Tr{
const int M=(<<)-,H=(<<)-;
int mi[M+],zuo[M+],you[M+]; void build(){
rep(i,H+,M){
zuo[i]=you[i]=i-H;
mi[i]=L+;
}
per(i,H,){
zuo[i]=zuo[i<<];
you[i]=you[i<<|];
mi[i]=L+;
}
} void cg(int x,int val){
x+=H;mi[x]=val;
while(x!=){
x>>=;
mi[x]=min(mi[x],val);
}
} int rt,ctr; bool ck(int miv,int pos){
return (rt+)<Q(pos,ctr)+miv;
//即 (rt-miv+1)<Q(pos,ctr)
//即 递减函数小于递增函数
} int calc(int x){
if(x==ctr) return ;
return min(max(rt-mi[x+H]+,),Q(ctr,x));
} int get_mi(int x,int oi){//oi=0 oi=1
while((x<<)<M){
if(mi[x<<|oi]<=rt) x=(x<<)|oi;
else x=(x<<)|(oi^);
}
return x;
} int Qr(int R,int C){//修改rt ctr
rt=R;ctr=C; int ans=,x,miv,pos; x=ctr+H;miv=L+;
while(x!=){
x>>=;
if(x&) continue;
if(ck(min(miv,mi[x|]),you[x|])) miv=min(mi[x|],miv);
else{
x|=;
break;
}
}
while((x<<)<M){
int ls=(x<<);
if(ck(min(miv,mi[ls]),you[ls])){
miv=min(miv,mi[ls]);
x=(x<<|);
}
else x=ls;
}
pos=x-H; ans=max(calc(pos),ans);
//往左的第一个 mi[pos]<=rt
while(x!=){
if(x&){
if(mi[x^]<=rt){
x=x^;
break;
}
}
x>>=;
}
pos=get_mi(x,)-H;
ans=max(calc(pos),ans); x=ctr+H;miv=L+;
while(x!=){
x>>=;
if(x&){
if(ck(min(miv,mi[x^]),zuo[x^])) miv=min(miv,mi[x^]);
else{
x^=;
break;
}
}
}
while((x<<)<M){
int rs=(x<<|);
if(ck(min(miv,mi[rs]),zuo[rs])){
miv=min(miv,mi[rs]);
x=(x<<);
}
else x=rs;
}
pos=x-H; ans=max(calc(pos),ans);
//往右第一个
while(x!=){
if((x&)==){
if(mi[x|]<=rt){
x|=;
break;
}
}
x>>=;
}
pos=get_mi(x,)-H; ans=max(calc(pos),ans); return ans;
}
}
using Tr::cg;
using Tr::Qr; struct Query{
int l,r,no;
}a[N];
bool cmp(Query aa,Query bb){
return aa.l>bb.l;
}
vector<int> ps[N];
int hd[N],no[N],bg[N]; typedef long long ll;
ll ans[N]; int main(){
scanf("%s",s+);
L=n=strlen(s+); m=rd();
rep(i,,m){
s[++n]='#';
scanf("%s",s++n);
bg[i]=n;
n+=strlen(s++n); rep(j,bg[i]+,n) hd[j]=i,no[j]=n-j+;
a[i]=(Query){rd(),rd(),i};
} Sa::init(); rep(i,,n){
rep(j,sa[i],n) cerr<<s[j];
cerr<<endl;
} rep(i,,n) ps[hd[sa[i]]].pb(i); sort(a+,a++m,cmp);
Tr::build();
int R=L;
rep(i,,m){
while(a[i].l<=R) cg(rk[R],R) ,R--;
rep(j,,ps[a[i].no].size()-){
int lcp=,pos=ps[a[i].no][j];
if(j) lcp=Q(pos,ps[a[i].no][j-]);
lcp=max(lcp,Qr(a[i].r,pos)); if(no[sa[pos]]>lcp) ans[a[i].no]+=no[sa[pos]]-lcp;
}
} rep(i,,m) printf("%lld\n",ans[i]);
return ;
}
//得到sa ht st存ht
//vector存每个询问的位置 //l从大到小对询问排序
//处理T的重复子串
//线段树加点 线段树上二分最小值
loj#2720. 「NOI2018」你的名字的更多相关文章
- LOJ 2720 「NOI2018」你的名字——后缀自动机
题目:https://loj.ac/problem/2720 自己总是分不清 “SAM上一个点的 len[ ] ” 和 “一个串的前缀在 SAM 上匹配的 len ”. 于是原本想的 68 分做法是, ...
- 【LOJ】#2720. 「NOI2018」你的名字
题解 把S串建一个后缀自动机 用一个可持久化权值线段树维护每个节点的right集合是哪些节点 求本质不同的子串我们就是要求T串中以每个点为结束点的串有多少在\(S[l..r]\)中出现过 首先我们需要 ...
- LOJ_#2720. 「NOI2018」你的名字 _后缀数组+主席树+倍增
题面: https://loj.ac/problem/2720 考虑枚举T串的每个后缀i,我们要做两件事. 一.统计有多少子串[i,j]在S中要求位置出现. 二.去重. 第二步好做,相当于在后缀数组上 ...
- 「NOI2018」你的名字
「NOI2018」你的名字 题目描述 小A 被选为了\(ION2018\) 的出题人,他精心准备了一道质量十分高的题目,且已经 把除了题目命名以外的工作都做好了. 由于\(ION\) 已经举办了很多届 ...
- LOJ #2721. 「NOI2018」屠龙勇士(set + exgcd)
题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...
- loj#2718. 「NOI2018」归程
题目链接 loj#2718. 「NOI2018」归程 题解 按照高度做克鲁斯卡尔重构树 那么对于询问倍增找到当前点能到达的高度最小可行点,该点的子树就是能到达的联通快,维护子树中到1节点的最短距离 s ...
- loj#2721. 「NOI2018」屠龙勇士
题目链接 loj#2721. 「NOI2018」屠龙勇士 题解 首先可以列出线性方程组 方程组转化为在模p意义下的同余方程 因为不保证pp 互素,考虑扩展中国剩余定理合并 方程组是带系数的,我们要做的 ...
- Loj #2719. 「NOI2018」冒泡排序
Loj #2719. 「NOI2018」冒泡排序 题目描述 最近,小 S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 *\(1\) 到 \(n\) 的排列*的冒泡排序. 下面是对冒泡排 ...
- loj 2719 「NOI2018」冒泡排序 - 组合数学
题目传送门 传送门 题目大意 (相信大家都知道) 显然要考虑一个排列$p$合法的充要条件. 考虑这样一个构造$p$的过程.设排列$p^{-1}_{i}$满足$p_{p^{-1}_i} = i$. 初始 ...
随机推荐
- 基于MAVEN使用IDEA创建dubbo入门项目图文教程
花了很长时间没有找到一个很详细的图文教程来学习dubbo入门框架,故记录下来. 一: 项目工程目录 简单介绍项目目录结构: 二: 创建父工程 具体操作步骤: 1,打开IDEA,按下面步骤来 File- ...
- node.js http接口调试时请求串行特性分析
缘起: 产品业务上有个类数据库服务的请求时间比较长(类似mysql的sql查询),为了优化减少并发时的请求数,做了一个并发时共用请求的优化. 通过单元测试后,想通过手动模拟看下效果,发现优化一直不能生 ...
- css的position
1.标准流2.浮动3.定位块级元素:div.H1-H6.有序及无序列表(ol.ul.li).p内联元素:a.span.img 1. 介绍 1.1 说明 Position 属性:规定元素的定位类型.即元 ...
- java_jsp和servlet中乱码问题
- unity解压缩zip发布后的一些问题
前段时间项目需要,搞了下zip的解压缩问题,也是利用ICSharpCode.SharpZipLib.dll来处理的zip,这里说下之前遇到的坑(这里提供我用的这个库ICSharpCode.SharpZ ...
- Android : Camera2/HAL3 框架分析
一.Android O上的Treble机制: 在 Android O 中,系统启动时,会启动一个 CameraProvider 服务,它是从 cameraserver 进程中分离出来,作为一个独立进程 ...
- android LogConfigurator
android LogConfigurator 此为第三方的 Log日志
- python学习笔记:1.初识python
4.26 今日内容大纲 1.初识计算机.CPU 内存 硬盘 2.python初识 3.python发展史以及影响 4.python的分类 5.python的种类 6.变量 7.常量 8.注释 9.基础 ...
- 记一次msyql导入导致的问题
公司有个项目要导入150M大小的sql文件,但是导入时报错,去网上找答案,很多人说是因为保留字什么什么的,所以就按照sql文件里面的mysql版本又去下载了一份mysql5.6安装好,但是登陆不了,又 ...
- nodejs03-GET数据处理
数据请求:--- 前台:form ajax jsonp 后台:一样 请求方式: 1.GET 数据在URL中 2.POST 数据在请求体中 请求数据组成: 头--header:url,头信息 身子--c ...