传送门

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

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

结论:答案等于所有本质不同的回文子串长之和减去字典序相邻的回文子串的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. CentOS7下 Python2.7.5升级为Python2.7.13

    参考:https://www.jianshu.com/p/fad3942fc0ed 第一步:查看Centos版本及Python版本 • CentOS版本 [root@ tools_package]# ...

  2. iOS-UIScrollView内容复用【实现两个试图的复用】

    前言 这里说的内容复用,是指添加到 ScrollView 里面的试图是同一个模型:比如,我需要在 ScrollView 上添加100个 xkView(其他封装好的VC.UIView),每次滑动 Scr ...

  3. docker微服务部署之:七、Rancher进行微服务扩容和缩容

    docker微服务部署之:六.Rancher管理部署微服务 Rancher有两个特色用起来很方便,那就是扩容和缩容. 一.扩容前的准备工作 为了能直观的查看效果,需要修改下demo_article项目 ...

  4. OS之进程管理---多处理器调度

    引言 之前我们所学习的操作系统进程调度策略的前提条件是单处理器系统下的CPU调度程序.如果系统中存在多个CPU,那么负载分配就成为可能,但是相应的调度问题就会更加复杂. 多处理器调度方法 对于多处理器 ...

  5. 关于editplus设置java和c#

    1.java设置 首先要在目录上手动新建一个class文件.放置编译好的class文件

  6. PHP 修改目录下所有与文件夹重名的前缀文件为index.后缀

    <?phpset_time_limit(0); function traverse($path = '.' , $dir_name='') { $current_dir = opendir($p ...

  7. 【优化】bigpipe技术

    一.什么是bigpipe Bigpipe是Facebook工程师提出了一种新的页面加载技术. BigPipe是一个重新设计的基础动态网页服务体系.大体思路是,分解网页成叫做Pagelets的小块,然后 ...

  8. 用PopupWindow做下拉框

    最近在做下拉框,本来想用spinner,可是spinner达不到项目要求,跟同学同事问了一圈,都在用popwindow, 网上看了一下,popwindow挺简单的,可定制性挺强的,符合我的要求,所以, ...

  9. Android 开发工具类 17_setAlarm

    Alarm 是在应用程序生命周期之外设置的,所以它们十分适合于调度定时更新或者数据查询,从而避免了在后台持续运行 Service.但触发 Alarm 时,就会广播指定的 Pending Intent. ...

  10. lintcode-->哈希函数

    在数据结构中,哈希函数是用来将一个字符串(或任何其他类型)转化为小于哈希表大小且大于等于零的整数.一个好的哈希函数可以尽可能少地产生冲突.一种广泛使用的哈希函数算法是使用数值33,假设任何字符串都是基 ...