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;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值