可能是一个 SAM 常用技巧?感觉 SAM 的基础题好多啊..

题目描述

给定一个长度为 \(n\) 的字符串 \(S\) ,令 \(T_i\) 表示它从第 \(i\) 个字符开始的后缀,求:

\[\sum_{1\le i<j\le n}len(T_i)+len(T_j)-2\times lcp(T_i,T_j)
\]

其中,\(len(a)\) 表示字符串 \(a\) 的长度,\(lcp(a,b)\) 表示字符串 \(a\) 和字符串 \(b\) 的最长公共前缀。

输入输出格式

输入格式:

一行,一个字符串 \(S\)。

输出格式:

一行,一个整数,表示所求值。

输入输出样例

输入样例:

ababc

输出样例:

54

数据范围与约定

对于 \(100\%\) 的数据,保证 \(2\le n\le 500000\),且均为小写字母。

题解:

这个题的主要考点是 SAM 求 lcp。

lcp 是最长公共前缀,而我们用的是后缀自动机。只需要把字符串翻转过来建立 SAM,得到的就是”前缀自动机“了。

类似后缀自动机的性质,我们建造 parent 树,则父亲总是儿子的前缀。而最简状态自动机上的每个点都有自己的 \(mn,mx\) 表示同一个 Right 集合能表示的最短和最长子串,因此任意两个状态的 lcp 长度就是它们在 parent 树上的最近公共祖先节点的 \(mx\)。

然后我们只需要计算任意两点的 lcp 即可,这个操作可以一次 dfs 搞定。注意统计时子树的根也要算上。

每个点做出的贡献是它的 Right 集合大小,可以通过在 parent 树上统计子树信息得到。

此外,前面的 \(\sum_{1\le i<j\le n}i+j\) 可以推导出来 \(O(n)\) 或 \(O(1)\) 求出来。

时间复杂度 \(O(n)​\)。

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
struct edge
{
int n,nxt;
edge(int n,int nxt)
{
this->n=n;
this->nxt=nxt;
}
edge(){}
}e[1000100];
int head[1000100],ecnt=-1;
void add(int from,int to)
{
e[++ecnt]=edge(to,head[from]);
head[from]=ecnt;
}
int n;
char s[500100];
int ch[26][1000100],mx[1000100],r[1000100],par[1000100],pcnt;
void build()
{
int p=pcnt=1;
for(int i=1;i<=n;++i)
{
int w=s[i]-'a';
int np=++pcnt;
mx[np]=mx[p]+1;
r[np]=1;
while(p&&!ch[w][p])
{
ch[w][p]=np;
p=par[p];
}
if(!p)
par[np]=1;
else
{
int q=ch[w][p];
if(mx[q]==mx[p]+1)
par[np]=q;
else
{
int nq=++pcnt;
mx[nq]=mx[p]+1;
while(p&&ch[w][p]==q)
{
ch[w][p]=nq;
p=par[p];
}
for(int j=0;j<26;++j)
ch[j][nq]=ch[j][q];
par[nq]=par[q];
par[q]=nq;
par[np]=nq;
}
}
p=np;
}
}
long long ans=0;
void dfs(int x)
{
long long tmp=r[x];
for(int i=head[x];~i;i=e[i].nxt)
{
dfs(e[i].n);
ans-=tmp*mx[x]*r[e[i].n]*2;
r[x]+=r[e[i].n];
tmp+=r[e[i].n];
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i)
ans+=3ll*i*(i-1);
ans>>=1;
std::reverse(s+1,s+1+n);
build();
for(int i=2;i<=pcnt;++i)
add(par[i],i);
dfs(1);
printf("%lld\n",ans);
return 0;
}

洛谷 P4248 / loj 2377 [AHOI2013] 差异 题解【后缀自动机】【树形DP】的更多相关文章

  1. 洛谷 P4248: bzoj 3238: [AHOI2013]差异

    题目传送门:洛谷 P4248. 题意简述: 定义两个字符串 \(S\) 和 \(T\) 的差异 \(\operatorname{diff}(S,T)\) 为这两个串的长度之和减去两倍的这两个串的最长公 ...

  2. BZOJ_3238_[Ahoi2013]差异_后缀自动机

    BZOJ_3238_[Ahoi2013]差异_后缀自动机 Description Input 一行,一个字符串S Output 一行,一个整数,表示所求值 Sample Input cacao Sam ...

  3. 洛谷 P3975 / loj 2102 [TJOI2015] 弦论 题解【后缀自动机】【拓扑排序】

    后缀自动机入门. 题目描述 为了提高智商,ZJY 开始学习弦论. 这一天,她在<String theory>中看到了这样一道问题:对于一个给定的长度为 \(n\) 的字符串,求出它的第 \ ...

  4. 洛谷AT2046 Namori(思维,基环树,树形DP)

    洛谷题目传送门 神仙思维题还是要写点东西才好. 树 每次操作把相邻且同色的点反色,直接这样思考会发现状态有很强的后效性,没办法考虑转移. 因为树是二分图,所以我们转化模型:在树的奇数层的所有点上都有一 ...

  5. 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)

    题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ...

  6. BZOJ 3238 [Ahoi2013]差异(后缀自动机)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3238 [题目大意] 给出一个串,设T[i]表示从第i位开始的后缀, 求sum(len( ...

  7. 洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)

    传送门 我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解…… 首先,对$S$和每一次的$T$都建一个SAM 先考虑一下$l=1,r=\left| S \right|$的情况 设$lim_i ...

  8. 【洛谷 P4248】 [AHOI2013]差异(后缀自动机)

    题目链接 \[ans=\sum_{1<=i<j<=n}len(T_i)+len(T_j)-2*lcp(T_i,T_j)\] 观察这个式子可以发现,前面两个\(len\)是常数,后面的 ...

  9. BZOJ3238: [Ahoi2013]差异 (后缀自动机)

    Description Input 一行,一个字符串S Output 一行,一个整数,表示所求值 Sample Input cacao Sample Output 54 HINT 2<=N< ...

随机推荐

  1. [Training Video - 4] [Groovy] Optional parameter in groovy

    Employee.log=log Employee e1 = new Employee() log.info e1.add(1,2,3,4) // optional parameters in gro ...

  2. Oracle RMAN-06023 和ORA-19693错误

    在将一个0级备份的数据库还原到其它机器上时,首先遇到了RMAN-06023然后遇到ORA-19693错误,错误发生的环境和内容大致如下: 数据库版本: SQL> select * from v$ ...

  3. CSS 伪类和伪元素--pseudo

    总结在前: 0. 参考资料 1. 伪类和伪元素是不同的两种东西. 2. 伪类和伪元素都属于CSS选择器. 3. CSS引入伪类和伪元素是为了实现基于文档树之外的信息,i.e. 段落的第一行,的格式化. ...

  4. atom插件记录

    ├── Zen@0.16.4 写作的时候用,很给力 ├── atom-beautify@0.29.9 美化一切代码 ├── autocomplete-paths@1.0.2 路径自动提示 ├── au ...

  5. Spring框架总结(九)

    三.AOP编程 关注点代码:除了业务代码以外的代码.比如开启事务,关闭事务,异常处理核心业务代码:保存用户这一句才是重点.例子如下:// 保存一个用户public void add(User user ...

  6. C++学习--第一个程序

    C++控制台应用程序 我们创建一个包含预编译头的C++控制台应用程序时,会发现其结构是这样的: 1)理解预编译头文件: 所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件( ...

  7. 深入理解java虚拟机(三)对象回收判断算法以及死亡过程

    在堆里面存放着Java几乎所有的对象实例,垃圾收集器要进行垃圾回收,要做的第一步便是找出那些对象是需要回收的. 怎么判断对象是否需要回收? 常用的方法有两种. 1.引用计数算法.为每一个对象添加一个引 ...

  8. Android-xx倍图

    图片文件夹对应倍图关系: ldpi 0.75 倍图 mdpi   1.0 倍图   hdpi    1.5 倍图 xhdpi  2.0 倍图 xxhdpi    3.0 倍图 xxxhdpi  4.0 ...

  9. thinkphp里数据嵌套循环

    做thinkphp时要用到循环里面嵌套循环的,并第二个循环是和外面的有关联的. thinkphp官网给出的文档为: <volist name="list" id=" ...

  10. WinForm心得

    如果有UI部分可以复用,那么可以单独设置为一个UserControl或者一个自定义子类控件 如果是单一一个控件,只是继承并修改了默认控件的样式行为,那么可以直接创建一个class并继承该控件,缺点是D ...