ACM-ICPC 2018 南京賽區網路預賽 I. Skr (馬拉車+字串hash/迴文自動機)
阿新 • • 發佈:2019-02-09
題意:
給你一個只有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;
}