题目链接:http://codeforces.com/gym/100548

今天晚上突然有了些兴致去学习一下数据结构,然后就各种无意中看到了Palindrome Tree的数据结构,据说是2014年新出的数据结构,也让我回想起了西安打铁时候的经历。这道题的题意其实是比较清晰的,给你两个长度200000的字符串,求它们有多少对回文子串。处理字符串有许多常用的工具,像后缀数组,后缀自动机,AC自动机,KMP,但是这些数据结构在针对回文串的处理上都不是特别强,当然稍微处理一下我们还是可以很好的利用好上面这些数据结构处理一些回文的问题。另外一个关于回文的问题就是求某个串的最长公共子串了,一个O(n)的Manachar算法算是解决了这个问题。Palindrome Tree似乎就是专门为了填补对于处理回文串的空白而产生的,而且最神的是,当你有了前面的一些数据结构的基础,再去看这个数据结构的整个结构,算法的流程,你会觉得很清晰,非常容易理解,当然这也是多亏了下面的博客给了我代码的模板以及对数据结构的一些理解。

http://blog.csdn.net/u013368721/article/details/42100363

http://blog.csdn.net/u013368721/article/details/42104207

下面是谈谈自己对上面博客阐述内容的一些个人的理解

首先是对数据结构的成员作一个简单的描述:
nxt[i][ch]   节点i在接收了字符ch之后所指向的节点
fail[i]      以节点i所指代的回文串的末尾字符结束的上一个回文串 fail[ ababa ] = aba  fail[aba] = a
cnt[i]       节点i所表示的回文串的个数
num[i]       以节点i所表示的回文串的末尾字符结束的回文串的个数
len[i]       节点i所表示的回文串的长度
S[i]         第i个字符
last         新添加的字符所产生的最长回文串的节点
p            节点的总的个数
n            当前字符串的长度

其中个人觉得比较难理解的是fail[i]. 假如i节点指代的回文串是"ababa"的话,那么fail[i]就是指以这个回文串的末尾字符结尾(就是'a')的上一个最长的回文串,也即是aba,再上一个自然就是a了。相似的是KMP里的失配指针,或者是在后缀自动机里上一个可以接收的后缀。

接下来就是整个算法的描述了

1.一开始初始化0号节点和1号节点,分别表示长度为偶数的节点(即空串),和1号节点(长度为奇数的空串),显然空串的长度是0,而这样的奇数串是不存在的,len[0]=0,len[1]=-1. 这个len[1]设成-1有着其独道的好处,具体的一些好处可以参看上面的博客。

2.接下来考虑添加1个新字符ch,首先要注意的是getFail()函数的作用,while (S[n - len[x] - 1] != S[n]) x = fail[x]; 的实际含义是找到一个能够接收当前新字符的回文串的编号,  对原串"abacaba"新加1个c,S[n-len[x]-1]=a != c, 所以回到上一个回文串节点,即表示aba的节点,这个时候S[n-len[x]-1]=c,满足题意,于是乎我们就找到了一个可以接收这个新状态的节点cur

3.接下来我们就可以更新nxt[cur][ch],当这个节点不存在的时候,我们需要新建这样的节点,长度是len[cur]+2,然后我们还需要知道新建的这个节点now的fail值,fail[now]=nxt[getFail(fail[cur])][ch],即当前新建立的节点的上一个回文串,getFail(fail[cur])返回的是以fail[cur]对应字符串结尾的可以接收ch的回文串。所以可以这么理解 now=nxt[getFail(cur)][ch],fail[now]=nxt[getFail(fail[cur])][ch].

4.然后更新一下新节点的num值,以及cnt值,注意的是这里的cnt值并不代表该节点对应的回文子串出现的总次数,正如后缀自动机里记数的时候cnt也不能表示这个字符串出现的总次数,最后需要有一个结果回加的过程,即是count的过程。

最后整个代码是有着比较清晰的结构的,下面对于这题的代码就是用了上面博客提供的模板。

对于原本的题目,需要做的就是在偶串中心节点0,和奇串中心节点1,不停地往两边塞字符就好了,当两边的状态都存在的时候继续往下dfs,最后统计一下结果即可。

这个数据结构维护的信息其实可以用作很多特别的用途。

1.直接dfs(0),dfs(1)我们可以递归的打印出所有回文串以及各自出现的次数

2.新添加1个字符是否能产生新的回文串,即可以在线的得出S的前缀i包含的不同的回文子串的个数。

3.num[i]可以得到对每个新增的字符为结尾的回文串的个数,以及len[i]求出以这个字符结尾的最长回文串长度

总之个人觉得这个数据结构可以做绝大多数的回文串的题目了,而且这个数据结构非常的elegent.字符串长度为n,字符集大小为m,则空间复杂度是O(nm),至于时间复杂度的话,我也不知道是怎么证明的,但是据说是O(nlogm),可以说对于字符集经常固定的题目来说就是一个O(n)的算法了。

#pragma warning(disable:4996)
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cstdio>
#include <algorithm>
using namespace std; #define MAXN 210000
#define ll long long struct PalindromeTree
{
/*static variables*/
const static int maxn = 210000;
const static int ch = 26;
/*
* nxt[i][ch] 节点i在接收了字符ch之后所指向的节点
* fail[i] 以节点i所指代的回文串的末尾字符结束的上一个回文串 fail[ ababa ] = aba fail[aba] = a
* cnt[i] 节点i所表示的回文串的个数
* num[i] 以节点i所表示的回文串的末尾字符结束的回文串的个数
* len[i] 节点i所表示的回文串的长度
* S[i] 第i个字符
* last 新添加的字符所产生的最长回文串的节点
* p 节点的总的个数
* n 当前字符串的长度
*/
int nxt[maxn][ch];
int fail[maxn];
int cnt[maxn];
int num[maxn];
int len[maxn];
int S[maxn];
int last;
int n;
int p; int newnode(int length){
memset(nxt[p], 0, sizeof(nxt[p]));
cnt[p] = num[p] = 0;
len[p] = length;
return p++;
} void init(){
p = 0;
newnode(0);
newnode(-1);
last = 0;
n = 0;
S[n] = -1;
fail[0] = 1;
} int getFail(int x){
while (S[n - len[x] - 1] != S[n]) x = fail[x];
return x;
} void add(int c) {
c -= 'a';
S[++n] = c;
int cur = getFail(last);
if (!nxt[cur][c]) {
int now = newnode(len[cur] + 2);
fail[now] = nxt[getFail(fail[cur])][c];
nxt[cur][c] = now;
num[now] = num[fail[now]] + 1;
}
last = nxt[cur][c];
cnt[last] ++;
} void count(){
for (int i = p - 1; i >= 0; --i){
cnt[fail[i]] += cnt[i];
}
}
}; PalindromeTree treex, treey;
char buf1[MAXN], buf2[MAXN]; ll ans; void dfs(int u, int v)
{
for (int i = 0; i < treex.ch; ++i){
int x = treex.nxt[u][i];
int y = treey.nxt[v][i];
if (x&&y){
ans += (ll)treex.cnt[x] * treey.cnt[y];
dfs(x, y);
}
}
} int main()
{
int T; cin >> T; int ca = 0;
while (T--)
{
treex.init();
treey.init();
scanf("%s%s", buf1, buf2);
int len1 = strlen(buf1), len2 = strlen(buf2);
for (int i = 0; i < len1; ++i) {
treex.add(buf1[i]);
}
for (int i = 0; i < len2; ++i){
treey.add(buf2[i]);
}
treex.count();
treey.count();
ans = 0;
dfs(0, 0);
dfs(1, 1);
printf("Case #%d: %I64d\n", ++ca, ans);
}
return 0;
}

The Problem to Slow Down You(Palindromic Tree)的更多相关文章

  1. 2014-2015 ACM-ICPC, Asia Xian Regional Contest G The Problem to Slow Down You 回文树

    The Problem to Slow Down You Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjud ...

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

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

  3. 回文自动机 + DFS --- The 2014 ACM-ICPC Asia Xi’an Regional Contest Problem G.The Problem to Slow Down You

    The Problem to Slow Down You Problem's Link: http://acm.hust.edu.cn/vjudge/problem/viewProblem.actio ...

  4. 回文树 Palindromic Tree

    回文树 Palindromic Tree 嗯..回文树是个什么东西呢. 回文树(或者说是回文自动机)每个节点代表一个本质不同的回文串. 首先它类似字典树,每个节点有SIGMA个儿子,表示对应的字母. ...

  5. CodeForcesGym 100548G The Problem to Slow Down You

    The Problem to Slow Down You Time Limit: 20000ms Memory Limit: 524288KB This problem will be judged ...

  6. The Problem to Slow Down You

    The Problem to Slow Down You 输入:t个测试样例,每个样例输入两个字符串 输出:这两对字符串的回文串可以组成多少对本质不同的回文串 题意:给你两个字符串,然后问你这两字符串 ...

  7. UVAlive 7041 The Problem to Slow Down You(回文树)

    题目链接: https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show ...

  8. HDU6430 Problem E. TeaTree【dsu on tree】

    Problem E. TeaTree Problem Description Recently, TeaTree acquire new knoledge gcd (Greatest Common D ...

  9. UVALive - 7041 The Problem to Slow Down You (回文树)

    https://vjudge.net/problem/UVALive-7041 题意 给出两个仅包含小写字符的字符串 A 和 B : 求:对于 A 中的每个回文子串,B 中和该子串相同的子串个数的总和 ...

随机推荐

  1. PHP.TP框架下商品项目的优化4-优化商品添加表单js

    优化商品添加表单js 思路 1.制作五个按钮 2.下面五个table 3.全部隐藏,点击则显示 4.点击第几个按钮就显示第几个table 具体操作 1.添加按钮 2.添加五个table并添加class ...

  2. C17K:Lying Island

    链接 题意: 有n个人,每个人可能会说: 第x个人是好人/坏人 如果第x个人是好人/坏人,则第y个人是好人/坏人 思路: 状压dp,首先每个人所说的人只能是他前面10个人,所以对于第i个人记录下,他前 ...

  3. Robocopy.exe使用详例

    Robocopy.exe使用详例           Robocopy.exe 是 微软在Windows server 2003 Resource Kit Tools 里面提供的程序来做备份的.Vis ...

  4. gprof使用介绍 (gcc -pg) [转]

    原文出处: http://blog.csdn.net/unbutun/article/details/6609498 linux服务端编程,性能总是不可避免要思考的问题. 而单机(严格的说是单核)单线 ...

  5. 《Cracking the Coding Interview》——第8章:面向对象设计——题目5

    2014-04-23 18:42 题目:设计一个在线阅读系统的数据结构. 解法:这题目太大了,我的个亲娘.显然你不可能一次加载一整本书,做到单页纸加载的粒度是很必要的.为了读书的连贯效果,预取个几页也 ...

  6. [译]17-spring基于java代码的配置元数据

    spring还支持基于java代码的配置元数据.不过这种方式不太常用,但是还有一些人使用.所以还是很有必要介绍一下. spring基于java代码的配置元数据,可以通过@Configuration注解 ...

  7. eclipse importing maven projects 卡顿

    导入一个maven工程后 一直显示 importing maven projects 9% 解决办法: 找到eclipse安装目录下的eclipse.ini 在最后加入 -vm $JAVA_HOME% ...

  8. elk-filebeat收集docker容器日志

    目录 使用docker搭建elk filebeat安装与配置 docker容器设置 参考文章 首发地址 使用docker搭建elk 1.使用docker-compose文件构建elk.文件如下: ve ...

  9. hdu 1203 01背包 I need a offer

    hdu 1203  01背包  I need a offer 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1203 题目大意:给你每个学校得到offe ...

  10. log4j配置打印mybatis的sql到控制台(复制)

    log4j.rootLogger=DEBUG, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender ...