PAM板子题
思路:所有的回文串长度好统计,manacher或者PAM都可以做,难点在于统计回文串内的字符种类
观察PAM中的增加字符的add函数的代码
// 节点:0为偶根,1为奇根。从2开始是有效节点。
int nxt[N][26]; // 类似Trie的指针
int fail[N]; // 失败指针/后缀链接
int len[N]; // 节点代表的回文串长度
int cnt[N]; // 节点代表的回文串出现次数(最后需要count函数统计)
int num[N]; // 以当前节点为结尾的回文串个数(沿着fail链)
int s[N]; // 存放添加的字符
int last; // 最后一个字符所在的节点
int n; // 已添加的字符数
int tot; // 总的节点数(2 ~ tot)
void add(int c) {
c -= 'a'; // 根据实际情况调整,如果输入是数字则不需要此句
s[++n] = c;
int cur = getFail(last); // 通过上一个节点的last找到可扩展的节点cur
if (!nxt[cur][c]) {
// 如果这个回文串还没有出现过,需要新建节点
int now = newNode(len[cur] + 2); // 新回文串长度是cur的长度+2
// 设置新节点的fail指针
// 从cur的fail节点开始,继续找可以扩展的后缀
fail[now] = nxt[getFail(fail[cur])][c];
// 注意:如果ch[getFail(fail[cur])][c]不存在怎么办?
// 由于奇根和偶根的存在,它一定会存在。最坏情况会指向单个字符c或空串。
nxt[cur][c] = now; // 将cur的c孩子指向新节点
num[now] = num[fail[now]] + 1; // 更新以新节点结尾的回文串个数
}
last = nxt[cur][c]; // 更新last
cnt[last]++; // 对这个节点的出现次数计数+1
// 注意:此时的cnt并不是最终的出现次数,还需要最后通过count函数累加
}
我们发现,函数内的cur节点是待添加字符c将要对其插入的节点,now是待添加字符c的新节点,相当于从cur结尾的回文串加了一种字符c,变成了now结尾的回文串,而我们又要统计字符种类,看c只有26种,考虑状态压缩
可以开一个数组col,在这上面执行 col[now] = (col[cur] | (1<<c)); 语句,相当于把字符种类压入至一个二进制数中
统计答案时,这个二进制数上的1的个数就是答案
这里推荐c++的函数__builtin_popcount(num),能够快速统计num在二进制下的1的个数,不用自己手写函数了
主函数如下
struct Palindromic_Tree { //仅小写字符
// 节点:0为偶根,1为奇根。从2开始是有效节点。
int nxt[N][26]; // 类似Trie的指针
int fail[N]; // 失败指针/后缀链接
int len[N]; // 节点代表的回文串长度
int cnt[N]; // 节点代表的回文串出现次数(最后需要count函数统计)
int num[N]; // 以当前节点为结尾的回文串个数(沿着fail链)
int s[N]; // 存放添加的字符
int last; // 最后一个字符所在的节点
int n; // 已添加的字符数
int tot; // 总的节点数(2 ~ tot)
int col[N];
// 总的不同回文子串个数就是 pam.tot - 2
int newNode(int L) {
// 初始化一个新节点,长度为L
Forl(i,0,25){
nxt[tot][i]=0;
}
cnt[tot] = num[tot] = 0;
len[tot] = L;
return tot++;
}
void init() {
// 初始化整个PAM
tot = 0;
n = last = 0;
// 创建两个根节点:偶根0 和 奇根1
newNode(0); // 偶根,长度为0
newNode(-1); // 奇根,长度为-1(虚拟节点)
s[0] = -1; // 设置第一个字符为特殊字符,防止越界
fail[0] = 1; // 偶根的fail指向奇根
fail[1] = 1; // 奇根的fail指向自己
}
int getFail(int x) {
// 从x节点开始,沿着fail链向上找,直到找到满足条件的节点
// 条件:s[n - len[x] - 1] == s[n]
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); // 通过上一个节点的last找到可扩展的节点cur
if (!nxt[cur][c]) {
// 如果这个回文串还没有出现过,需要新建节点
int now = newNode(len[cur] + 2); // 新回文串长度是cur的长度+2
// 设置新节点的fail指针
// 从cur的fail节点开始,继续找可以扩展的后缀
fail[now] = nxt[getFail(fail[cur])][c];
// 注意:如果ch[getFail(fail[cur])][c]不存在怎么办?
// 由于奇根和偶根的存在,它一定会存在。最坏情况会指向单个字符c或空串。
col[now]=(col[cur]|(1<<c)); //压入字符种类
nxt[cur][c] = now; // 将cur的c孩子指向新节点
num[now] = num[fail[now]] + 1; // 更新以新节点结尾的回文串个数
}
last = nxt[cur][c]; // 更新last
cnt[last]++; // 对这个节点的出现次数计数+1
// 注意:此时的cnt并不是最终的出现次数,还需要最后通过count函数累加
}
void count() {
// 统计每个回文串的真实出现次数
// 因为一个长回文串的出现次数会包含它的所有回文后缀的出现次数
// 所以需要按len从大到小遍历,累加到fail指针指向的节点上
Forr(i,tot-1,2) { // 从编号大的节点开始(长度也大)
cnt[fail[i]] += cnt[i];
}
// 注意:两个根节点(0和1)的cnt无实际意义,通常从2开始使用。
}
}PAM;
char str[N];
int Lstr;
void InfiniteLight() {
read(str+1);
Lstr=strlen(str+1);
PAM.init();
Forl(i,1,Lstr) {
PAM.add(str[i]);
}
PAM.count();
long long totnum=0;
Forl(i,2,PAM.tot-1) {
totnum+=(long long)__builtin_popcount(PAM.col[i])*PAM.cnt[i]; //计算答案
}
println(totnum);
return ;
}

京公网安备 11010502036488号