题目链接
题解:
如果求出 字符串中不同的子串数量 和 反转后的字符串中不同子串的数量,那么两种的字符串合在一起之后的不同的字符串数量再 / 2 就是答案。
但是如果原来的字符串是回文串的话,那么回文串本来应该要计算两次的,但是发现它只计算了一次,所以要加上回文串的数量再 / 2。
具体做法,求不同子串的数量可以用后缀数组,先将原字符串反转加在后面,中间用一个其他字符隔开。回文子串个数可以用回文树。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
#define P pair<ll, int>
#define debug(x) cout << #x << ": " << x << " "
#define fastio ios::sync_with_stdio(false), cin.tie(0)
const int mod = 1e9 + 7;
const int M = 1000000 + 10;
const int N = 400000 + 10;
ll n, rk[N];
ll k, tmp[N];
bool compare_sa(int i,int j){
if(rk[i]!=rk[j]) return rk[i]<rk[j];
else{
int x=i+k<=n?rk[i+k]:-1;
int y=j+k<=n?rk[j+k]:-1;
return x<y;
}
}
void calc_sa(string s,int *sa){
for(int i=0;i<=n;i++){
sa[i]=i;
rk[i]=i<n?s[i]:-1;
}
for(k=1;k<=n;k*=2){
sort(sa,sa+n+1,compare_sa);
tmp[sa[0]]=0;
for(int i=1;i<=n;i++){
tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0);
}
for(int i=0;i<=n;i++){
rk[i]=tmp[i];
}
}
}
void calc_lcp(string s,int *sa,int *lcp){
for(int i=0;i<=n;i++) rk[sa[i]]=i;
int h=0;
lcp[0]=0;
for(int i=0;i<n;i++){
int j=sa[rk[i]-1];
if(h>0) h--;
for(;j+h<n&&i+h<n;h++){
if(s[j+h]!=s[i+h]) break;
}
lcp[rk[i]-1]=h;
}
}
const int MAX = 200000 + 10;
const int ALP = 26;
struct Palindromic_Tree {
int son[MAX][ALP]; //转移边
int fail[MAX]; //fail 指针
int cnt[MAX]; //当前节点表示的回文串在原串中出现了多少次
int num[MAX]; //当前节点 fail 可以向前跳多少次
int len[MAX]; //当前节点表示的回文串的长度
int S[MAX]; //插入的字符串
int last; //最后一次访问到的节点,类似 SAM
int n; //插入的字符串长度
int p; //自动机的总状态数
int newnode(int l) {
memset(son[p], 0, sizeof(son[p]));
cnt[p] = num[p] = 0;
len[p] = l;
return p++;
}
void init() {
p = 0;
newnode(0);
newnode(-1);
last = n = 0;
S[n] = -1;
fail[0] = 1;
}
int get_fail(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 = get_fail(last); //通过上一次访问的位置去扩展
if (!son[cur][c]) { //如果没有对应的节点添加一个新节点
int now = newnode(len[cur] + 2);
fail[now] = son[get_fail(fail[cur])][c]; //通过当前节点的 fail 去扩展出新的 fail
son[cur][c] = now;
num[now] = num[fail[now]] + 1; //记录 fail 跳多少次
}
last = son[cur][c];
cnt[last]++; //表示当前节点访问了一次
}
void count() {
//如果某个节点出现一次,那么他的 fail 也一定会出现一次,并且在插入的时候没有计数
for (int i = p - 1; i >= 0; i--) cnt[fail[i]] += cnt[i];
}
} AUT;
string s;
int sa[N], height[N];
signed main()
{
cin >> s;
ll len = n = s.length();
string st = s; reverse(s.begin(), s.end()); s = st + '#' + s;
n = 2 * n + 1; calc_sa(s, sa), calc_lcp(s, sa, height);
ll p = 0;
for(int i = 1; i < n; i ++) p += height[i];
p = n * (n + 1) / 2 - (len + 1) * (len + 1) - p;
AUT.init();
for(int i = 0; i < len; i ++) AUT.add(s[i]);
ll q = AUT.p - 2;
cout << (p + q) / 2 << endl;
return 0;
}
/*
Rejoicing in hope, patient in tribulation.
*/