题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5421
这题就是学一下回文树前段插入。
下面这个是论文原话。
因此对于我们最初的板子,我们有:
改动1:原本维护上一个节点所在的回文串的节点的指针---->两个指针(重点)
由于回文串最长回文前缀和后缀是一样的,所以实际上我们只需要在左右插入的时候维护两个不同的指针,左边插入的指针维护他右边那个字符所在的最长回文串所在节点,右边插入的指针维护他上一个字符所在的最长回文串所在节点。
论文中所说的需要特判的地方是什么呢?
就是插入某个字符时,如果当前节点的回文串长度=加入的字符总数,那么将另一边的指针改成当前回文串节点即可。
这样两边的指针永远指向最长的回文串。
改动2:沿着fail匹配的过程左右不相同
左边的匹配右边,右边的匹配左边,这个的话小问题,即代码中两个get_fail的区别。
下面是针对本题的:
操作1:直接前段插入即可
操作2:直接后端插入即可
操作3:节点总数-2即可
操作4:维护num数组表示当前i节点代表的回文串的回文后缀数目(原本的回文树里也有),一个变量cnt维护当前回文串总数,每插入一个字符,cnt就加上当前节点所在回文串的num,这样也能做到O(1)输出
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<map>
#include<set>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define rof1(i,a,b) for (int i=a;i>=b;i--)
#define rof0(i,a,b) for (int i=a;i>b;i--)
#define pb push_back
#define fi first
#define se second
#define debug(x) printf("----Line %s----\n",#x)
#define pt(x,y) printf("%s = %d\n",#x,y)
#define INF 0x3f3f3f3f
#define df(x) ll x;scanf("%I64d",&x)
#define df2(x,y) ll x,y;scanf("%I64d %I64d",&x,&y)
#define mod 1000000007
#define duozu(T) int T;scanf("%d",&T);while (T--)
const int N = 1e5+5;
const int maxn = 1e5+5;
const int ALP = 26;
struct PAM{ // 每个节点代表一个回文串
int next[maxn][ALP]; // next指针,参照Trie树
int fail[maxn]; // fail失配后缀链接
int num[maxn];
int len[maxn]; // 回文串长度
int s[maxn<<1]; // 存放添加的字符
int l_last,r_last; //指向上一个字符所在的节点,方便下一次add
int l,r; //r-l+1表示现在添加的字符数量
int p; // 节点个数
ll cnt;//记录当前回文串总数
int newnode(int w){//新建一个节点,长度初始化为这个节点代表的回文串长度
for(int i=0;i<ALP;i++)
next[p][i] = 0;
num[p] = 0;
len[p] = w;
return p++;
}
void init(){
cnt = 0;
p = 0;
newnode(0);
newnode(-1);
l_last = r_last = 0;
memset(s,-1,sizeof s);//这样就不用特判边界了
l = maxn;
r = maxn-1;///pam中的s数组开了两倍,l,r都在中间
fail[0] = 1;
}
int get_fail_l(int x){
while (s[l+len[x]+1] != s[l]) x = fail[x];
return x;
}
int get_fail_r(int x){
while (s[r-len[x]-1] != s[r]) x = fail[x];
return x;
}
void ladd(int c){
c -= 'a';
s[--l] = c;
int cur = get_fail_l(l_last);
if (!next[cur][c]){
int now = newnode(len[cur]+2);
fail[now] = next[get_fail_l(fail[cur])][c];///直接父节点开始匹配就变成了自己,所以从父节点的最长回文后缀开始匹配
next[cur][c] = now; ///从这里也能开出如果父节点最长回文后缀不存在,那么当前最长回文后缀就是最后一个字母
num[now] = num[fail[now]] + 1;
}
l_last = next[cur][c];
cnt += num[l_last];
if (len[l_last]==r-l+1) r_last = l_last;
}
void radd(int c){
c -= 'a';
s[++r] = c;
int cur = get_fail_r(r_last);
if (!next[cur][c]){
int now = newnode(len[cur]+2);
fail[now] = next[get_fail_r(fail[cur])][c];
next[cur][c] = now;
num[now] = num[fail[now]] + 1;
}
r_last = next[cur][c];
cnt += num[r_last];
if (len[r_last]==r-l+1) l_last = r_last;
}
}pam;
int main()
{
int q;
while (~scanf("%d",&q)){
pam.init();
while (q--){
int op;
char s[2];
scanf("%d",&op);
if (op==1) scanf("%s",s),pam.ladd(s[0]);
if (op==2) scanf("%s",s),pam.radd(s[0]);
if (op==3) printf("%d\n",pam.p-2);
if (op==4) printf("%lld\n",pam.cnt);
}
}
return 0;
}