The value of a string ss is equal to the number of different letters which appear in this string.
Your task is to calculate the total value of all the palindrome substring.
Input
The input consists of a single string |s|(1 \le |s| \le 3 \times 10^5)∣s∣(1≤∣s∣≤3×105).
The string ss only contains lowercase letters.
Output
Output an integer that denotes the answer.
样例输入复制
abac
样例输出复制
6
样例解释
abacabac has palindrome substrings a,b,a,c,abaa,b,a,c,aba,ans the total value is equal to 1+1+1+1+2=61+1+1+1+2=6。
编辑代码
题意:
求所有回文串中,每一个回文串的贡献为不同字符的和
分析:
回文自动机可以求出所有的不同的回文串和起个数cnt[i],我们只需要知道该回文串有多少字符即可。
区间种类数:
但这题预处理也行,因为字母的个数就26,我们开一下sum[i][j]表示1~i的字母为j的个数。
回文自动机+树状数组
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN=300005;
int last[MAXN],nxt[MAXN];
LL ans[MAXN*10];
int tot=0;
const int N=26;
char s[MAXN];
struct Query
{
int l,r,id;
} q[MAXN*10];
bool cmp(Query a,Query b)
{
if(a.l==b.l)
return a.r<b.r;
return a.l<b.l;
}
struct Palindromic_Tree
{
int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[MAXN] ; //表示以节点i表示的回文串的最右端点为回文串结尾的回文串个数
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
int S[MAXN] ;//存放添加的字符
int last_pos[MAXN];//存放回文串结尾的位置
//int id[MAXN];//回文子串结尾的地方+1 ,与last_pos功能一样
int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
int n ;//表示添加的字符个数。
int p ;//表示添加的节点个数。求不同回文串的个数=p-2(要跑count函数)
int newnode ( int l ) //新建节点
{
for ( int i = 0 ; i < N ; ++ i )
next[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
last_pos[p]=0;
len[p] = l ;
return p ++ ;
}
void init () //初始化
{
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}
int get_fail ( int x ) //和KMP一样,失配后找一个尽量最长的
{
while ( S[n - len[x] - 1] != S[n] )
x = fail[x] ;
return x ;
}
int add(int c,int pos)///获得以pos位置开始的回文串个数(注意倒着插入)
{
c -= 'a' ;
S[++ n] = c ;
int cur = get_fail(last) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) //如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
{
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
next[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
last_pos[now]=pos;
}
last = next[cur][c] ;
cnt[last]++;
//id[last]=n;//当前回文子串结尾的地方+1
return num[last];
}
void countt ()
{
for ( int i = p - 1 ; i >= 0 ; -- i )
cnt[fail[i]] += cnt[i] ;
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
///遍历所有回文串
void all()
{
//从2开始,因为0和1为根节点
for(int i=2; i<p; i++)
{
//id[i]为结尾+1,len为长度,所以起点为id[i]-len[i],终点为id[i]-1
//这样就可以保存所有回文子串的位置和长度的信息了
q[++tot]=Query{last_pos[i]-len[i]/2+1,last_pos[i]+1,tot};
/*unordered_set<char> st;
for(int j=last_pos[i]-len[i]/2; j<=last_pos[i]; j++)
{
st.insert(s[j]);
if(st.size()>=26)
break;
}
ans+=cnt[i]*st.size(); */
}
}
} pam;
int lens;
//树状数组只能计算A[1]开始的和,A[0]这个元素是不能用的。
int c[MAXN];//c[i]==A[i]+A[i-1]+...+A[i-lowbit(i)+1]
//返回i的二进制最右边1的值
int lowbit(int i)
{
return i&(-i);
}
//返回A[1]+...A[i]的和
LL sum(int x)
{
LL sum = 0;
while(x)
{
sum += c[x];
x -= lowbit(x);
}
return sum;
}
//令A[i] += val
void add(int x, LL val)
{
while(x <=lens) //注意这里的n,是树状数组维护的长度
{
c[x] += val;
x += lowbit(x);
}
}
int main()
{
scanf("%s",s);
lens = strlen(s);
pam.init();
for(int i=0; i<lens; i++)
{
pam.add(s[i],i);
}
for(int i=1; i<=lens; i++)
{
int val=s[i-1]-'a';
if(last[val]==0)
add(i,1LL);
else
nxt[last[val]]=i;
last[val]=i;
}
pam.countt();
pam.all();
sort(q+1,q+1+tot,cmp);
int l=1;
for(int i=1; i<=tot; i++)
{
for(; l<q[i].l; l++)
{
add(l,-1LL);
if(nxt[l])
add(nxt[l],1LL);
}
ans[q[i].id]+=sum(q[i].r);
}
LL sum=0;
for(int i=1; i<=tot; i++)
{
sum+=(LL)ans[i]*pam.cnt[i+1];
}
printf("%lld\n",sum);
return 0;
}
回文自动机+前缀求和
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN=300005;
LL sum[MAXN][30];
LL ans;
int tot=0;
const int N=26;
char s[MAXN];
struct Query
{
int l,r,id;
} q[MAXN*10];
bool cmp(Query a,Query b)
{
if(a.l==b.l)
return a.r<b.r;
return a.l<b.l;
}
struct Palindromic_Tree
{
int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[MAXN] ; //表示以节点i表示的回文串的最右端点为回文串结尾的回文串个数
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
int S[MAXN] ;//存放添加的字符
int last_pos[MAXN];//存放回文串结尾的位置
//int id[MAXN];//回文子串结尾的地方+1 ,与last_pos功能一样
int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
int n ;//表示添加的字符个数。
int p ;//表示添加的节点个数。求不同回文串的个数=p-2(要跑count函数)
int newnode ( int l ) //新建节点
{
for ( int i = 0 ; i < N ; ++ i )
next[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
last_pos[p]=0;
len[p] = l ;
return p ++ ;
}
void init () //初始化
{
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}
int get_fail ( int x ) //和KMP一样,失配后找一个尽量最长的
{
while ( S[n - len[x] - 1] != S[n] )
x = fail[x] ;
return x ;
}
int add(int c,int pos)///获得以pos位置开始的回文串个数(注意倒着插入)
{
c -= 'a' ;
S[++ n] = c ;
int cur = get_fail(last) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) //如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
{
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
next[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
last_pos[now]=pos;
}
last = next[cur][c] ;
cnt[last]++;
//id[last]=n;//当前回文子串结尾的地方+1
return num[last];
}
void countt ()
{
for ( int i = p - 1 ; i >= 0 ; -- i )
cnt[fail[i]] += cnt[i] ;
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
///遍历所有回文串
void all()
{
//从2开始,因为0和1为根节点
for(int i=2; i<p; i++)
{
//id[i]为结尾+1,len为长度,所以起点为id[i]-len[i],终点为id[i]-1
//这样就可以保存所有回文子串的位置和长度的信息了
int num = 0;
for(int j = 0; j < 26; ++j)
if(sum[last_pos[i]+1][j]-sum[last_pos[i]-len[i]/2-1+1][j])
num++;
ans += 1ll*cnt[i]*num;
}
}
} pam;
int lens;
int main()
{
scanf("%s",s);
lens = strlen(s);
pam.init();
for(int i=0; i<lens; i++)
{
pam.add(s[i],i);
for(int j = 0; j<26; ++j)
sum[i+1][j] = sum[i][j];
sum[i+1][s[i]-'a']++;
}
pam.countt();
pam.all();
printf("%lld\n",ans);
return 0;
}