HackerRank Special Substrings 回文树+后缀自动机+set
既然要求对每个前缀都求出答案,不难想到应该用回文树求出所有本质不同的回文子串。
然后考虑如何对这些回文子串的前缀进行去重。
结论:答案等于所有本质不同的回文子串长之和减去字典序相邻的回文子串的LCP长度之和。
这个结论其实不难理解。可以回忆后缀数组经典题目:求一个字符串本质不同的子串个数。道理是一样的。
然后就有思路了,从空串开始每次加一个字符,用一个set维护当前所有本质不同的回文子串(只存左右端点),如果产生了新的回文子串就扔进set里跟前驱后继xjb更新一下答案。
字典序比较用后缀数组会比较方便。然而我不会写后缀数组,那就后缀自动机求LCP硬上好了。注意由于这题只需要考虑回文子串,所以正着反着都是一样的,这么一来也就不必对反串建后缀自动机,直接对原串建个自动机然后求最长公共后缀就行了。
注意这题用LCP比较字典序的时候需要对LCP是否超出串长进行特判,细节虽然不多但比较容易忽视。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
const int maxn=300005;
namespace SAM{
int root,last,cnt=0,val[maxn<<1]={0},par[maxn<<1]={0},go[maxn<<1][26]={0};
vector<int>G[maxn<<1];
int id[maxn<<1],tim=0,rnk[maxn],f[maxn][19],log_tbl[maxn];
void initalize();
void extend(int,int);
void dfs(int);
int LCP(int,int);
}
using SAM::LCP;
namespace PAM{
int last,cnt,go[maxn][26],val[maxn],par[maxn];
int extend(int);
}
struct A{
int l,r;
A(int l,int r):l(l),r(r){}
bool operator<(const A &a)const;
};
char s[maxn];
int n;
int main(){
scanf("%d",&n);
scanf("%s",s+1);
SAM::initalize();
PAM::par[0]=PAM::cnt=1;
PAM::val[1]=-1;
set<A>DS;
long long ans=0;
for(int i=1;i<=n;i++){
int l=PAM::extend(i);
if(l){
set<A>::iterator u=DS.lower_bound(A(l,i)),v=u;
if(u==DS.end()){
if(!DS.empty())u=DS.find(*DS.rbegin());
}
else{
if(u!=DS.begin())u--;
else u=DS.end();
}
if(u!=DS.end()&&v!=DS.end())
ans+=min(min(u->r-u->l,v->r-v->l)+1,LCP(u->r,v->r));
ans+=i-l+1;
if(u!=DS.end())ans-=min(min(i-l,u->r-u->l)+1,LCP(i,u->r));
if(v!=DS.end())ans-=min(min(i-l,v->r-v->l)+1,LCP(i,v->r));
DS.insert(A(l,i));
}
printf("%lld\n",ans);
}
return 0;
}
namespace SAM{
void initalize(){
root=last=cnt=1;
for(int i=1;i<=n;i++)extend(s[i]-'a',i);
for(int i=2;i<=cnt;i++)G[par[i]].push_back(i);
dfs(1);
log_tbl[0]=-1;
for(int i=1;i<=n;i++)log_tbl[i]=log_tbl[i>>1]+1;
for(int j=1;(1<<j)<n;j++)
for(int i=1;i+(1<<j)-1<n;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
void extend(int c,int v){
int p=last,np=++cnt;
val[np]=val[p]+1;
while(p&&!go[p][c]){
go[p][c]=np;
p=par[p];
}
if(!p)par[np]=root;
else{
int q=go[p][c];
if(val[q]==val[p]+1)par[np]=q;
else{
int nq=++cnt;
val[nq]=val[p]+1;
memcpy(go[nq],go[q],sizeof(go[q]));
par[nq]=par[q];
par[np]=par[q]=nq;
while(p&&go[p][c]==q){
go[p][c]=nq;
p=par[p];
}
}
}
id[np]=v;
last=np;
}
void dfs(int x){
if(id[x]){
if(tim)f[tim][0]=val[last];
rnk[id[x]]=++tim;
last=x;
}
for(int i=0;i<(int)G[x].size();i++)dfs(G[x][i]);
last=par[x];
}
int LCP(int l,int r){
if(l==r)return l;
l=rnk[l];
r=rnk[r];
if(l>r)swap(l,r);
r--;
int k=log_tbl[r-l+1];
return min(f[l][k],f[r-(1<<k)+1][k]);
}
}
namespace PAM{
int extend(int i){
int p=last,c=s[i]-'a';
while(s[i]!=s[i-val[p]-1])p=par[p];
if(!go[p][c]){
int q=++cnt,now=p;
val[q]=val[p]+2;
do p=par[p];while(s[i]!=s[i-val[p]-1]);
par[q]=go[p][c];
last=go[now][c]=q;
return i-val[q]+1;
}
else last=go[p][c];
return 0;
}
}
bool A::operator<(const A &a)const{
if(r==a.r)return false;
int t=LCP(r,a.r);
if(t>r-l+1&&t>a.r-a.l+1)return r-l+1>a.r-a.l+1;
if(t>r-l+1)return true;
if(t>a.r-a.l+1)return false;
return s[r-t]<s[a.r-t];
}
HackerRank Special Substrings 回文树+后缀自动机+set的更多相关文章
- 回文树&后缀自动机&后缀数组
KMP,扩展KMP和Manacher就不写了,感觉没多大意思. 之前感觉后缀自动机简直可以解决一切,所以不怎么写后缀数组. 马拉车主要是通过对称中心解决问题,有的时候要通过回文串的边界解决问题 ...
- BZOJ 3676 [Apio2014]回文串 (后缀自动机+manacher/回文自动机)
题目大意: 给你一个字符串,求其中回文子串的长度*出现次数的最大值 明明是PAM裸题我干嘛要用SAM做 回文子串有一个神奇的性质,一个字符串本质不同的回文子串个数是$O(n)$级别的 用$manach ...
- BZOJ 3676: [Apio2014]回文串 后缀自动机 Manacher 倍增
http://www.lydsy.com/JudgeOnline/problem.php?id=3676 过程很艰难了,第一次提交Manacher忘了更新p数组,超时,第二次是倍增的第0维直接在自动机 ...
- [APIO2014]回文串 后缀自动机_Manancher_倍增
Code: // luogu-judger-enable-o2 #include <cstdio> #include <algorithm> #include <cstr ...
- [模板] 回文树/回文自动机 && BZOJ3676:[Apio2014]回文串
回文树/回文自动机 放链接: 回文树或者回文自动机,及相关例题 - F.W.Nietzsche - 博客园 状态数的线性证明 并没有看懂上面的证明,所以自己脑补了一个... 引理: 每一个回文串都是字 ...
- Palindromic Tree 回文自动机-回文树 例题+讲解
回文树,也叫回文自动机,是2014年被西伯利亚民族发明的,其功能如下: 1.求前缀字符串中的本质不同的回文串种类 2.求每个本质不同回文串的个数 3.以下标i为结尾的回文串个数/种类 4.每个本质不同 ...
- 省选算法学习-回文自动机 && 回文树
前置知识 首先你得会manacher,并理解manacher为什么是对的(不用理解为什么它是$O(n)$,这个大概记住就好了,不过理解了更方便做$PAM$的题) 什么是回文自动机? 回文自动机(Pal ...
- 回文树/回文自动机(PAM)学习笔记
回文树(也就是回文自动机)实际上是奇偶两棵树,每一个节点代表一个本质不同的回文子串(一棵树上的串长度全部是奇数,另一棵全部是偶数),原串中每一个本质不同的回文子串都在树上出现一次且仅一次. 一个节点的 ...
- 回文树(回文自动机)(PAM)
第一个能看懂的论文:国家集训队2017论文集 这是我第一个自己理解的自动机(AC自动机不懂KMP硬背,SAM看不懂一堆引理定理硬背) 参考文献:2017国家集训队论文集 回文树及其应用 翁文涛 参考博 ...
随机推荐
- Java并发工具类之同步屏障CyclicBarrier
CyclicBarrier的字面意思是可以循环使用的Barrier,它要做的事情是让一个线程到达一个Barrier的时候被阻塞,直到最后一个线程到达Barrier,屏障才会放开,所有被Barrier拦 ...
- Elasticsearch批量操作API用法介绍
Elasticsearch的Bulk API允许批量提交index和delete请求,有如下两种用法: 用法1 BulkRequestBuilder requestBuilder = client.p ...
- iOS----线程之间的通信
当线程的数量大于一个的时候,线程之间可能会产生通信,既一个线程产生的结果要被另一个线程用到. 比如常用的图片的加载就是这个样子.图片的加载是在子线程进行的,当图片加载完毕,就会回到主线程中刷新UI,展 ...
- 项目实体类使用@Data注解,但是项目业务类中使用getA(),setA()方法报错,eclipse中配置lombok
@Data注解来源与Lombok,可以减少代码中大量的set get方法,大量减少冗余代码,但是今天部署项目时候,发现实体类使用@Data注解,但是项目业务类中使用getA(),setA()方法报错. ...
- git问题--Push rejected: Push to origin/master was rejected
解决git问题 Push rejected: Push to origin/master was rejected 意思是git拒绝合并两个不相干的东西 此时你需要在打开Git Bash,然后进入相应 ...
- 03——Solr学习之Solr的使用(不会用)
1.先放上次在linux搭建成功的solr管理UI界面 2.有个很蛋疼的问题我就要吐槽一下了 由于没接触过solr这玩意,在百度上一顿操作搜索怎么用,怎么导入数据,建索引库什么的,看了一大片别人的博客 ...
- 配置豆瓣镜像作为python 库的下载源
配置豆瓣镜像作为python 库的下载源 Windows 下如下配置:
- 【Java】认识 JDK,JRE,JVM
JDK,JRE,JVM 今天我们讨论下这三个Java工具 JDK 全称Java Development ToolKit(Java 开发工具包). JDK是整个JAVA的核心,其包括了Java运行环境( ...
- Android中实时预览UI和编写UI的各种技巧
一.啰嗦 之前有读者反馈说,你搞这个所谓的最佳实践,每篇文章最后就给了一个库,感觉不是很高大上.其实,我在写这个系列之初就有想过这个问题.我的目的是:给出最实用的库来帮助我们开发,并且尽可能地说明这个 ...
- Windows 8家长控制
不多说,直接干货! 此刻,限制小孩使用电脑时间已经完成!!! 欢迎大家,加入我的微信公众号:大数据躺过的坑 人工智能躺过的坑 同时,大家可以关注我的个人博客: http ...