传送门

既然要求对每个前缀都求出答案,不难想到应该用回文树求出所有本质不同的回文子串。

然后考虑如何对这些回文子串的前缀进行去重。

结论:答案等于所有本质不同的回文子串长之和减去字典序相邻的回文子串的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的更多相关文章

  1. 回文树&后缀自动机&后缀数组

    KMP,扩展KMP和Manacher就不写了,感觉没多大意思.   之前感觉后缀自动机简直可以解决一切,所以不怎么写后缀数组.   马拉车主要是通过对称中心解决问题,有的时候要通过回文串的边界解决问题 ...

  2. BZOJ 3676 [Apio2014]回文串 (后缀自动机+manacher/回文自动机)

    题目大意: 给你一个字符串,求其中回文子串的长度*出现次数的最大值 明明是PAM裸题我干嘛要用SAM做 回文子串有一个神奇的性质,一个字符串本质不同的回文子串个数是$O(n)$级别的 用$manach ...

  3. BZOJ 3676: [Apio2014]回文串 后缀自动机 Manacher 倍增

    http://www.lydsy.com/JudgeOnline/problem.php?id=3676 过程很艰难了,第一次提交Manacher忘了更新p数组,超时,第二次是倍增的第0维直接在自动机 ...

  4. [APIO2014]回文串 后缀自动机_Manancher_倍增

    Code: // luogu-judger-enable-o2 #include <cstdio> #include <algorithm> #include <cstr ...

  5. [模板] 回文树/回文自动机 && BZOJ3676:[Apio2014]回文串

    回文树/回文自动机 放链接: 回文树或者回文自动机,及相关例题 - F.W.Nietzsche - 博客园 状态数的线性证明 并没有看懂上面的证明,所以自己脑补了一个... 引理: 每一个回文串都是字 ...

  6. Palindromic Tree 回文自动机-回文树 例题+讲解

    回文树,也叫回文自动机,是2014年被西伯利亚民族发明的,其功能如下: 1.求前缀字符串中的本质不同的回文串种类 2.求每个本质不同回文串的个数 3.以下标i为结尾的回文串个数/种类 4.每个本质不同 ...

  7. 省选算法学习-回文自动机 && 回文树

    前置知识 首先你得会manacher,并理解manacher为什么是对的(不用理解为什么它是$O(n)$,这个大概记住就好了,不过理解了更方便做$PAM$的题) 什么是回文自动机? 回文自动机(Pal ...

  8. 回文树/回文自动机(PAM)学习笔记

    回文树(也就是回文自动机)实际上是奇偶两棵树,每一个节点代表一个本质不同的回文子串(一棵树上的串长度全部是奇数,另一棵全部是偶数),原串中每一个本质不同的回文子串都在树上出现一次且仅一次. 一个节点的 ...

  9. 回文树(回文自动机)(PAM)

    第一个能看懂的论文:国家集训队2017论文集 这是我第一个自己理解的自动机(AC自动机不懂KMP硬背,SAM看不懂一堆引理定理硬背) 参考文献:2017国家集训队论文集 回文树及其应用 翁文涛 参考博 ...

随机推荐

  1. Flask从入门到精通之Jinja2模板引擎

    我们使用一个简单的例子切入到Jinja2模板引擎,形式最简单的Jinja2模板引擎就是一个包含响应文本的文件,实例如下: <h1>Hello World!</h1> 最简单的包 ...

  2. Redis 的 Sentinel

    Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务: 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服 ...

  3. SpringMVC整合kaptcha(验证码功能)

    一.依赖 <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha& ...

  4. Java基础梳理(一)

    List和Set比较,各自的子类比较 对比一:Arraylist与LinkedList的比较 1.ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高 ...

  5. jade直接写类似JavaScript语法的东西,不需要写script

    我们知道,html做计算都是在JavaScript中完成的,那么不用JavaScript行不行呢,可以直接在jade中一样的编写 如: -var a = 3 -var b = 4 div a+b = ...

  6. Mac 下安装node.js(转载)

    原文地址:http://blog.csdn.net/u011619283/article/details/52368759 Node.js 简介 Node.js 是一个用Chrome's V8 Jav ...

  7. java8新特性-入门摘要

    本文是针对java8做的入门摘要笔录,详细分析可参见如下原文. 原文地址 http://www.javacodegeeks.com/2013/02/java-8-from-permgen-to-met ...

  8. linux中查找某端口,并关闭对应的端口

    1,netstat -ntlp  (n表示不反向域名杰斯 t表示查看tcp协议的连接 l查看正在监听端口 p获取进程号和端口) 2,然后直接kill -9 端口号 参考全文:https://linux ...

  9. [转] TCP/IP原理、基础以及在Linux上的实现

    导言:本篇作为理论基础,将向我们讲述TCP/IP的基本原理以及重要的协议细节,并在此基础上介绍了TCP/IP在LINUX上的实现. OSI参考模型及TCP/IP参考模型 OSI模型(open syst ...

  10. 纯C++安卓开发 (ndk)系列之 ---- 常见问题

    常见问题1:run as Android Application运行时提示无法识别到模拟器 解决步骤如下: (1)首先查看安卓模拟器是否已经打开 (2)如果安卓模拟器已经打开,则操作步骤为:点击Ecl ...