题意:
给你一个只有0~9的数字组成的字符串,定义一个字符串的价值就是他表示的数字,"120"的价值是120
问你在字符串内不同的回文子串的价值总和是多少
解析:
马拉车算法求最大回文子串
马拉车算法里面匹配回文串的时候,其实就已经遍历过字符串内所有不同的回文子串至少一遍
那么我们就可以在马拉车算法里面由于回文串匹配的while()里面来处理就可以了。
然后这里因为马拉车算法匹配的串是经过扩充的,添加了'#'。那么我们只有在匹配出的回文串两端的
字符是'#',才进行计算该回文串的贡献。
因为在字符串中,任意一个回文子串"1......1",在扩充过的串中一定还能延伸成"#1.....1#"
那么"1.....1"这个回文串就会被算两次,所以我们只在两端是"#"的时候,才计算贡献。
如果你在两端是数字的时候计算贡献也行,只不过你要重新推出
扩充字符串数字位的下标转换成原字符串位置的下标的公式
这些其实按照马拉车算法的原理,理解推理一下就可以得出来。
然后就是字符串判重,用于判断这个回文串是否已经被计算过了。
这个就用字符串hash,就可以了。我一开始用unordered_set存已经被计算过的字符串,疯狂T...
后来发现用hash链表是最快的......将字符串hash后的值,按照取模后的值,对应插到该余数的链表中
然后这个模的数对时间的影响也是很大的,我一开始用1e5+7T了,变大成2e6+7就过了...
用时0.3s
#include <vector>
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <map>
#include<unordered_map>
#define IO ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll MOD = 1e9+7;
const ull mod= 2000007;
const ull base=13331;
const int MAXN = 2e6+10;
int hund[MAXN];
char t[MAXN*2];
int n;
int a[MAXN];
int p[MAXN*2];
char b[MAXN];
int ans;
int flag;
ull h[MAXN],has[MAXN];
struct node
{
ull w;
int next;
node(){}
node(ull w,int next)
{
this->w=w;
this->next=next;
}
}rode[MAXN*2];
int head[mod],tot;
void add(ull x)
{
int pos=x%mod;
rode[tot]=node(x,head[pos]);
head[pos]=tot++;
}
int find(ull x)
{
int pos=x%mod;
for(int i=head[pos];~i;i=rode[i].next)
if(rode[i].w==x)return 1;
return 0;
}
void init(int m)
{
memset(head,-1,sizeof(head));
tot=0;
h[0]=1;
for(int i=1;i<=m;i++)
h[i]=h[i-1]*base;
}
inline ull getlr(int l,int r)
{
l++;
r++;
return has[r]-has[l-1]*h[r-l+1];
}
void Manacher(char s[]) { //s从0开始
// Insert '#'
//t从1开始
t[0]='$';
t[1]='#';
int cnt=2;
for (int i = 0; i < n; ++i) {
t[cnt++]= s[i];
t[cnt++]= '#';
}
int x,y;
// Process t
//vector<int> p(t.size(), 0);
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < cnt; ++i) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) //匹配回文串
{
flag=t[i + p[i]]=='#'?1:0;
++p[i];
if(flag)
{
x=(i-p[i])/2;
y=p[i]-1;
int inc=1ll*(a[x]-a[x+y]+MOD)%MOD*hund[n-x-y]%MOD; //计算字符串价值
ull tmp=getlr(x,x+y-1); //计算字符串hash值
if(find(tmp)==0)
{
add(tmp);
ans=(ans+inc)%MOD;
}
}
}
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
}
//return s.substr((resCenter - resLen) / 2, resLen - 1); //在原串中起始位置((resCenter - resLen) / 2),长度为resLen-1
}
ll pow(ll a, ll n, ll p) //快速幂 a^n % p
{
ll ans = 1;
while(n)
{
if(n & 1) ans = ans * a % p;
a = a * a % p;
n >>= 1;
}
return ans;
}
ll niYuan(ll a, ll b) //费马小定理求逆元
{
return pow(a, b - 2, b);
}
void reverseString(string & str) {
int i=0, j = str.length()-1;
while(i < j) {
std::swap(str[i++], str[j--]);
}
}
int main() {
//IO
scanf("%s",b);
n=strlen(b);
init(n);
hund[0]=1;
hund[1]=niYuan(10,MOD);
for(int i=2;i<=n;i++)
{
hund[i]=1ll*hund[i-1]*hund[1]%MOD;
}
int past=1;
//reverseString(b);
a[n]=0;
has[0]=0;
for(int i=0;i<n;i++) //计算字符串hash值
{
has[i+1]=has[i]*base+(b[i]-'0');
}
for(int i=n-1;i>=0;i--) //计算字符串价值
{
a[i]=((1ll*past*(b[i]-'0'))+a[i+1])%MOD;
past=1ll*past*10%MOD;
}
Manacher(b);
printf("%d\n",ans);
return 0;
}
回文自动机+dfs,用时0.18s
#include <queue>
#include <cstdio>
#include <set>
#include <string>
#include <stack>
#include <cmath>
#include <climits>
#include <map>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#define ULL unsigned long long
typedef long long ll;
using namespace std;
const int MAXN = 2e6+10 ;
const ll MOD =1e9+7;
const int N = 11 ;
char s[MAXN];
int hund[MAXN];
struct Palindromic_Tree
{
int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
//int cnt[MAXN] ;
//int num[MAXN] ; // 当前节点通过fail指针到达0节点或1节点的步数(fail指针的深度)
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度
int S[MAXN] ;//存放添加的字符
int last ;//指向上一个字符所在的节点,方便下一次add
int n ;//字符数组指针
int p ;//节点指针
int newnode(int l) //新建节点
{
for(int i = 0 ; i < N ; ++ i) next[p][i] = 0 ;
//cnt[p] = 0 ;
//num[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) //失配后,在回文串x中的所有后缀里找到一个串右端+s[n]依然构成回文串
{ //这里因为一定是从长的找到最短的,所以找到的一定是最长的
while(S[n - len[x] - 1] != S[n]) x = fail[x] ;//判断此时S[n-len[last]-1]是否等于S[n]
//即上一个串-1的位置和新添加的位置是否相同,相同则说明构成回文,否则,last=fail[last]。
return x ;
}
void add(int c,int pos) //cur,last,now都代表一个字符串,而不是一个下标/字符
{
//printf("%d:",p);
c -= '0';
S[++ n] = c ; //n代表字符下标
int cur = get_fail(last) ; //通过上一个回文串找这个回文串的匹配位置
//printf("%d ",cur); //c+cur+c代表以c结尾的最长的回文串,cur对应原串中的位置就是以c前一个字符结尾的子串的位置
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 ;
//for(int i=pos-len[now]+1; i<=pos; ++i) printf("%c",s[i]);
} last = next[cur][c] ;
//cnt[last] ++ ;
//putchar(10);
}
void count()
{
//for(int i = p - 1 ; i >= 0 ; -- i) cnt[fail[i]] += cnt[i] ;
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
} run;
int ans;
void dfs(int u,int val)
{
int v,inc;
int tmp;
for(int i=0;i<10;i++)
{
if(run.next[u][i])
{
v=run.next[u][i];
tmp=run.len[v]-1?hund[run.len[v]-1]:0;
inc=(val+i+1ll*i*tmp%MOD)%MOD;
ans=(ans+inc)%MOD;
dfs(v,1ll*inc*10%MOD);
}
}
}
int main()
{
scanf("%s",&s);
int n=strlen(s);
run.init();
for(int i=0; i<n; i++) run.add(s[i],i);
hund[0]=1;
for(int i=1;i<=n;i++) hund[i]=1ll*hund[i-1]*10%MOD;
dfs(0,0);
dfs(1,0);
printf("%d\n",ans);
return 0;
}