1. 程式人生 > >ACM-ICPC 2018 南京賽區網路預賽 I. Skr (馬拉車+字串hash/迴文自動機)

ACM-ICPC 2018 南京賽區網路預賽 I. Skr (馬拉車+字串hash/迴文自動機)

題目連結

題意:

給你一個只有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;

}