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 ; }