传送门

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

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

结论:答案等于所有本质不同的回文子串长之和减去字典序相邻的回文子串的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. NPM install 中:-save 、 -save-dev 和 没有--save的情况

    原文地址:https://www.cnblogs.com/limitcode/p/7906447.html npm install moduleName 命令 . 安装模块到项目node_module ...

  2. JAVA并发编程学习笔记------多线程调优

    1. 多线程场景下尽量使用并发容器代替同步容器 (如ConcurrentHashMap代替同步且基于散列的Map, 遍历操作为主要操作的情况下用CopyOnWriteArrayList代替同步的Lis ...

  3. POST 请求的 forHTTPHeaderField

    Response Headers(从服务器得到的回复的头) Field name Description Example Status Access-Control-Allow-Origin Spec ...

  4. rabbitmq系列四 之路由

    1.路由 在上一个的教程中,我们构建了一个简单的日志记录系统.我们能够向许多接收者广播日志消息. 在本次教程中,我们向该系统添加一些特性,比如,我只需要严重错误(erroe级别)的部分日志打印到磁盘文 ...

  5. c#连接oracle遇到的问题

    1.最近在做项目,发现一个很有意思的现象.我在项目中引用System.Data.OracleClient进行oracle库的远程连接,一直出错.后来无意中,将.net framework 4.0框架改 ...

  6. 漫谈NIO(2)之Java的NIO

    1.前言 上章提到过Java的NIO采取的是多路IO复用模式,其衍生出来的模型就是Reactor模型.多路IO复用有两种方式,一种是select/poll,另一种是epoll.在windows系统上使 ...

  7. 【数组】Minimum Size Subarray Sum

    题目: Given an array of n positive integers and a positive integer s, find the minimal length of a sub ...

  8. Ubuntukylin-14.04-desktop(带分区)安装步骤详解

    不多说,直接上干货! 成功! Ubuntukylin-14.04-desktop( 不带分区)安装步骤详解 Ubuntukylin-14.04-desktop( 不带分区)安装步骤详解 Ubuntu1 ...

  9. android学习-Activity启动过程详解

    注:只是说明启动activity的过程(ActivityThread如何与ActivityManagerService简称AmS进行进程间通信调用全过程),不解析android从zygote(受精卵) ...

  10. [转] Ubuntu 14.04/14.10下安装VMware Workstation 11图文教程

    点击这里查看原文 译者:GuiltyMan 本文由 Linux公社翻译组 原创翻译  Linux公社 诚意奉献 更多请访问此处博客网站 VMware workstation 是一个可以进行桌面操作的虚 ...