传送门

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

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

结论:答案等于所有本质不同的回文子串长之和减去字典序相邻的回文子串的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. QQ个人信息保护 | 攻的对面叫防

    近来我们愈来愈重视个人信息保护,当接到骚扰电话时,你心里或许在想(我手机号怎么又被别人知道的?别人是怎么知道我手机号的?),现在的时代,手机已或不可缺,QQ几乎每个人都在用.而有些人总想保护个人QQ资 ...

  2. 转载:在spring中嵌入activemq

    转载:http://www.dev26.com/blog/article/137 web开发站中的邮件发送使用了activemq我这是从网上找的进行了一些修改,记录下来,为了避免发送邮件时程序对用户操 ...

  3. PHP之旅4 php 超全局变量

    预定义数组: 自动全局变量---超全局数组 1.包含了来自web服务器,客户端,运行环境和用户输入的数据 2.这些数组比较特别 3.全局范围内自动生效,都可以直接使用这些数组 4.用户不能自定义这些数 ...

  4. CSS3盒子模型(下)

    绝对定位的盒子水平/垂直居中 普通的盒子是左右margin 改为 auto就可, 但是对于绝对定位就无效了 定位的盒子也可以水平或者垂直居中,有一个算法. 首先left 50% 父盒子的一半大小 然后 ...

  5. android权限permission大全(权限提醒)

    1.Android.permission.WRITE_USER_DICTIONARY允许应用程序向用户词典中写入新词 2.android.permission.WRITE_SYNC_SETTINGS写 ...

  6. (转)Python - 字符串对齐

    https://zhuanlan.zhihu.com/p/33923344-----------Python小知识:用format格式化输出字符串 版权声明:本文为博主原创文章,未经博主允许不得转载. ...

  7. 基于Web实现网络拓扑图

    想想好像好久没用写博客了! 由于最近想跳槽了(ps:尽管公司挽留,提出一些异与往常的挽留“制度”,But确实已经死心了) ,发现前一段时间一些做Hadoop,和Spark同事时常来请教网络拓扑图的有关 ...

  8. http2.0之头部压缩

    什么是头部压缩?为什么要头部压缩? 我们知道,http请求和响应都是由[状态行.请求/响应头部.消息主题]三部分组成的. 一般而言,消息主体都会经过gzip压缩,或者本身传输的就是压缩过后的二进制文件 ...

  9. PlanetTogether APS安装与配置

    一.PT 安装 1.PT Server与Client的下载 下载网址:https://download.planettogether.com/ 2.安装.net framework 4.6.2 安装P ...

  10. Eclipse *的安装(图文详解)

    不多说,直接上干货!  前期博客 Eclipse *下载 可以直接解压,再配置JDK,即可使用. 结束 欢迎大家,加入我的微信公众号:大数据躺过的坑        人工智能躺过的坑       同时, ...