[LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字

题意

给定一个大串 \(S\) 以及 \(q\) 次询问, 每次询问给定一个串 \(T\) 和区间 \([l,r]\), 求 \(T\) 中有多少本质不同的子串不是 \(S[l:r]\) 的子串.

\(|S|\le 5\times 10^5,q\le 10^5,\sum|T|\le10^6\).

题解

普通的码农字符串题...

获得成就: \(40\texttt{min}(2400\texttt{s})\) 内打完 \(3.9\texttt{kB}\) 的代码(然而并没有打完就A...还是太菜了...)

感觉考场上如果T1T2没打满的话写个 \(68\) 分沙雕SAM暴力(询问区间都是原串的 \(17\) 个测试点)就可以跑路了...分高好写还不用调...

个人的大体思路是: 因为求本质不同子串个数是容易的, 所以先补集转化为求 \(T\) 的所有本质不同的子串中是 \(S[l:r]\) 的串的个数.

按照套路我们维护一个类似扫描线的东西, 用SAM对 \(T\) 的所有下标 \(i\) 求出以 \(i\) 为右端点且是 \(S[l:r]\) 的子串的最长子串长度. 按照SAM的套路, 这部分的计算就是直接用 \(T\) 在 \(S\) 的SAM上面跑, 如果可以匹配就匹配, 不能匹配跳到后缀自动机的父亲节点上来移动左端点.

按照上面这样计算是对整串来说的. 因为还要考虑区间 \([l,r]\) 的事情, 我们用线段树合并维护出每个结点的right集合, 设当前匹配长度为 \(len\), 那么只有当当前状态的right集合与 \([l+len-1,r]\) 有交集才说明与 \(S[l:r]\) 匹配. 如果不满足这个条件, 不能按照SAM的普通套路直接跳prt, 而是应该让 \(len\) 减少 \(1\). 直接跳的话左端点会移动若干个位置, 可能会跳过最优长度. SAM普通套路直接跳prt是因为如果 \(len\) 只减少 \(1\) 而没有到达prt的长度的话依然没有改变当前状态不能匹配的事实.

然而直接这样计算铁定会有重复, 我们对 \(T\) 的反串建SA求出所有前缀的最长公共后缀长度作为去重的参考信息. 按照SA求本质不同子串个数的套路, 重复的子串必然出现在后缀数组上相邻的两个后缀(注意是反串的后缀)上. 假设相邻的两个后缀 \(i,j\) 的在原串的对应前缀的最大匹配长度分别是 \(mlen_i,mlen_j\) 且它们的LCP是 \(height\) 的话, 贡献就是 \(mlen_j-\min(height,mlen_i)\).

实际上 \(mlen_j\) 肯定不会小于 \(\min(height,mlen_i)\), 因为这部分串是完全一样的. 所以直接减就行了.

以前用map的写法被UOJ 64位指针debuff给卡内存了qaq...然而指针线段树依然在被卡内存...UOJ变97分了QAQ

参考代码

之前听说今天minusT要更新Subterranean Rose? 完蛋在学校看不了qaq

#include <bits/stdc++.h>

const int MAXN=1e6+10;
typedef long long intEx; struct Node{
int l;
int r;
int sum;
Node* lch;
Node* rch;
Node(int,int);
void Insert(int);
int Query(int,int);
};
Node* N[MAXN]; int n;
int q;
int cnt=1;
int root=1;
int last=1;
int s[MAXN];
int SA[MAXN];
int len[MAXN];
int prt[MAXN];
int buc[MAXN];
int mlen[MAXN];
char buf[MAXN];
int rank[MAXN];
int height[MAXN];
int chd[MAXN][26];
int* x=new int[MAXN];
int* y=new int[MAXN]; void BuildSAM();
void Extend(char);
void BuildSA(char*,int);
Node* Merge(Node*,Node*); int main(){
freopen("name.in","r",stdin);
freopen("name.out","w",stdout);
scanf("%s",buf+1);
n=strlen(buf+1);
for(int i=1;i<=n;i++)
Extend(buf[i]);
BuildSAM();
scanf("%d",&q);
while(q--){
int l,r;
scanf("%s",buf+1);
scanf("%d%d",&l,&r);
int m=strlen(buf+1);
int cur=root,curlen=0;
for(int i=1;i<=m;i++){
int x=buf[i]-'a';
while(cur!=root&&!chd[cur][x]){
cur=prt[cur];
curlen=len[cur];
}
if(chd[cur][x]){
++curlen;
cur=chd[cur][x];
while(cur!=root&&!N[cur]->Query(l+curlen-1,r)){
--curlen;
if(curlen<=len[prt[cur]])
cur=prt[cur];
}
}
mlen[i]=curlen;
}
std::reverse(buf+1,buf+m+1);
BuildSA(buf,m);
intEx ans=0;
int last=0;
for(int i=1;i<=m;i++){
ans+=(m-SA[i]+1)-height[i];
last=std::min(height[i],last);
ans-=mlen[m-SA[i]+1]-last;
last=mlen[m-SA[i]+1];
}
printf("%lld\n",ans);
}
return 0;
} void BuildSAM(){
memset(buc,0,sizeof(int)*(n+1));
for(int i=1;i<=cnt;i++)
++buc[len[i]];
for(int i=1;i<=n;i++)
buc[i]+=buc[i-1];
for(int i=cnt;i>=1;i--)
s[buc[len[i]]--]=i;
for(int i=cnt;i>=1;i--)
N[prt[s[i]]]=Merge(N[prt[s[i]]],N[s[i]]);
} void Extend(char ch){
int p=last;
int x=ch-'a';
int np=++cnt;
last=np;
len[np]=len[p]+1;
N[np]=new Node(1,n);
N[np]->Insert(len[np]);
while(p&&!chd[p][x])
chd[p][x]=np,p=prt[p];
if(!p)
prt[np]=root;
else{
int q=chd[p][x];
if(len[q]==len[p]+1)
prt[np]=q;
else{
int nq=++cnt;
memcpy(chd[nq],chd[q],sizeof(chd[q]));
N[nq]=new Node(1,n);
len[nq]=len[p]+1;
prt[nq]=prt[q];
prt[q]=nq;
prt[np]=nq;
while(p&&chd[p][x]==q)
chd[p][x]=nq,p=prt[p];
}
}
} void Node::Insert(int x){
++this->sum;
if(this->l!=this->r){
int mid=(this->l+this->r)>>1;
if(x<=mid){
if(this->lch==NULL)
this->lch=new Node(this->l,mid);
this->lch->Insert(x);
}
else{
if(this->rch==NULL)
this->rch=new Node(mid+1,this->r);
this->rch->Insert(x);
}
}
} int Node::Query(int l,int r){
if(l<=this->l&&this->r<=r)
return this->sum;
else{
int ans=0;
int mid=(this->l+this->r)>>1;
if(l<=mid&&this->lch)
ans+=this->lch->Query(l,r);
if(mid+1<=r&&this->rch)
ans+=this->rch->Query(l,r);
return ans;
}
} Node* Merge(Node* a,Node* b){
if(a==NULL)
return b;
if(b==NULL)
return a;
Node* N=new Node(a->l,b->r);
N->sum=a->sum+b->sum;
N->lch=Merge(a->lch,b->lch);
N->rch=Merge(a->rch,b->rch);
return N;
} void BuildSA(char* s,int n){
int m=127;
memset(buc+1,0,sizeof(int)*m);
for(int i=1;i<=n;i++)
++buc[x[i]=s[i]];
for(int i=1;i<=m;i++)
buc[i]+=buc[i-1];
for(int i=n;i>=1;i--)
SA[buc[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;
memset(buc+1,0,sizeof(int)*m);
for(int i=1;i<=n;i++)
++buc[x[i]];
for(int i=1;i<=m;i++)
buc[i]+=buc[i-1];
for(int i=n;i>=1;i--)
SA[buc[x[y[i]]]--]=y[i];
std::swap(x,y);
x[SA[1]]=1;
p=1;
for(int i=2;i<=n;i++)
x[SA[i]]=(y[SA[i]]==y[SA[i-1]]&&y[SA[i]+k]==y[SA[i-1]+k])?p:++p;
if(p>=n)
break;
m=p;
}
for(int i=1;i<=n;i++)
rank[SA[i]]=i;
int k=0;
for(int i=1;i<=n;i++){
if(rank[i]==1)
continue;
if(k)
--k;
int j=SA[rank[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])
++k;
height[rank[i]]=k;
}
} Node::Node(int l,int r):l(l),r(r),sum(0),lch(NULL),rch(NULL){}

[LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字的更多相关文章

  1. NOI 2018 你的名字

    因为机房里的小伙伴都在看<你的名字.>而我不想看 所以来写了这道题... 给一个 $S$ 串,$q$ 次询问,每次一个 $T$ 串,问 $T$ 有多少没在 $S[l,r]$ 中以子串形式出 ...

  2. NOI 2018 你的名字 (后缀自动机+线段树合并)

    题目大意:略 令$ION2017=S,ION2018=T$ 对$S$建$SAM$,每次都把$T$放进去跑,求出结尾是i的前缀串,能匹配上$S$的最长后缀长度为$f_{i}$ 由于$T$必须在$[l,r ...

  3. [LOJ 2718][UOJ 393][BZOJ 5415][NOI 2018]归程

    [LOJ 2718][UOJ 393][BZOJ 5415][NOI 2018]归程 题意 给定一张无向图, 每条边有一个距离和一个高度. 再给定 \(q\) 组可能在线的询问, 每组询问给定一个点 ...

  4. [LOJ 2721][UOJ 396][BZOJ 5418][NOI 2018]屠龙勇士

    [LOJ 2721][UOJ 396][BZOJ 5418][NOI 2018]屠龙勇士 题意 题面好啰嗦啊直接粘LOJ题面好了 小 D 最近在网上发现了一款小游戏.游戏的规则如下: 游戏的目标是按照 ...

  5. NOI 2018 酱油记

    转眼离 NOI 2018 已经过了一个星期了,退役的我还是随便来水水吧. 语法.错字之类的可能会很多,但是我也不拘这点小节了. 恭喜 yww, zjt, sk 进队,zwl, myh au , yay ...

  6. [LOJ 2146][BZOJ 4873][Shoi2017]寿司餐厅

    [LOJ 2146][BZOJ 4873][Shoi2017]寿司餐厅 题意 比较复杂放LOJ题面好了qaq... Kiana 最近喜欢到一家非常美味的寿司餐厅用餐. 每天晚上,这家餐厅都会按顺序提供 ...

  7. Loj #2479. 「九省联考 2018」制胡窜

    Loj #2479. 「九省联考 2018」制胡窜 题目描述 对于一个字符串 \(S\),我们定义 \(|S|\) 表示 \(S\) 的长度. 接着,我们定义 \(S_i\) 表示 \(S\) 中第 ...

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

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

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

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

随机推荐

  1. Python制作回合制手游外挂简单教程(上)

    引入: 每次玩回合制游戏的时候,反反复复的日常任务让人不胜其烦 玩问道的时候,我们希望能够自动刷道,玩梦幻希望能自动做师门.捉鬼等等 说明: 该外挂只能模拟鼠标键盘操作,并不能修改游戏数据 我这里使用 ...

  2. [NOI 2016]国王饮水记

    Description 题库链接 给出 \(n\) 个水杯,每个水杯装有不同高度的水 \(h_i\) ,每次可以指定任意多水杯用连通器连通后断开,问不超过 \(k\) 次操作之后 \(1\) 号水杯的 ...

  3. EF批量操作数据与缓存扩展框架

    前言 在原生的EF框架中,针对批量数据操作的接口有限,EF扩展框架弥补了EF在批量操作时的接口,这些批量操作包括:批量修改.批量查询.批量删除和数据缓存,如果您想在EF中更方便的批量操作数据,这个扩展 ...

  4. Thinkphp 图片上传

    案例:广告的增删改查 步骤: 1引用 js 2 填写 input type=" file" 的id 3 填写 url 4后台保存地址 5前台成功后的处理 广告添加页 <div ...

  5. 【Tomcat】Tomcat集群session管理

    网上资料汇总: 关于 tomcat 集群中 session 共享的三种方法 Tomcat7集群共享Session 基于redis进行统一管理

  6. 获取物化视图定义语句的SQL

    老系统里总有人用物化视图,然后新同事们就得去FixBug 然后就遇到怎么查看物化视图定义语句的问题了 分享下,祝顺利! DBA权限下执行: select dbms_metadata.get_ddl(' ...

  7. python3.8 新特性

    https://docs.python.org/3.8/whatsnew/3.8.html python 3.8的新功能本文解释了与3.7相比,python 3.8中的新特性. 有关完整的详细信息,请 ...

  8. vue-resource获取不了数据,和ajax的区别,及vue-resource用法

    前几天用vue-resource调用接口,用post方式给后端,发现后端php接受不到数据,这好奇怪,最后发现提交给后端的时候 需要加一个参数 就是:emulateJSON : true 这句话的意思 ...

  9. LOJ#6085. 「美团 CodeM 资格赛」优惠券(set)

    题意 题目链接 Sol 考虑不合法的情况只有两种: 进去了 再次进去 没进去 但是出来了 显然可以用未知记录抵消掉 直接开个set维护一下所有未知记录的位置 最优策略一定是最后一次操作位置的后继 同时 ...

  10. 【代码笔记】iOS-自定义alertView

    一,效果图. 二,代码. ViewController.h #import <UIKit/UIKit.h> @interface ViewController : UIViewContro ...