题目链接: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. Android 自定义WebView 实现可以加载缓存数据

    1.自定义WebView说明 1.1.这个WebView可以加载缓存的数据.(需要后端配合,将html转换成一个字符串,主要是图片要用特殊格式) 1.2.注入了图片链接,为了方便点击webView中的 ...

  2. WPF开发实例——仿QQ登录界面

    原文:WPF开发实例--仿QQ登录界面 版权声明:本文为博主原创文章,如需转载请标明转载地址 http://blog.csdn.net/u013981858 https://blog.csdn.net ...

  3. React-router4简约教程

    React-router4简约教程 教程       webEmmet 17年10月   React-router和React-router-dom的选择 很多刚使用react的同学在接触到react ...

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

    2014-04-23 17:45 题目:假设有个呼叫中心,有接线员.经理.主管三种角色.如果接线员无法处理呼叫,就上传给经理:如果仍无法处理,则上传给主管.请用代码描述这一过程. 解法:第一眼觉得这题 ...

  5. Percona-Tookit工具包之pt-slave-find

      Preface       If we want to check out how many slaves in our replication environment.We can execut ...

  6. 关于在虚拟机上安装iOS所遇到的问题

    先配一张成功的图片: 要想实现上图所示的,就要安装 unlocker206, 安装完unlocker206 不用改里面的内容!!!看到**经验上说还要改内容,结果我试了很多次,根本不行,其实问题不在改 ...

  7. ymal

    https://blog.csdn.net/beginya/article/details/76768968 https://www.jianshu.com/p/97222440cd08

  8. Python全栈工程师(包、模块 的导入)

    ParisGabriel                每天坚持手写  一天一篇  决定坚持几年 为了梦想 为了信仰     Python人工智能从入门到精通 $ pip3 install tenso ...

  9. Linux 查看当前日期和时间

    一.查看和修改Linux的时区 1. 查看当前时区 命令 : "date -R" 2. 修改设置Linux服务器时区 方法 A 命令 : "tzselect" ...

  10. 在python中如何比较两个float类型的数据是否相等

    奇怪的现象 前几天跟同事聊起来,在计算机内部float比较是很坑爹的事情.比方说,0.1+0.2得到的结果竟然不是0.3? >>> 0.1+0.2 0.300000000000000 ...