題意:
給你一個只有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;
}