ACM-ICPC 2018 南京赛区网络预赛 I.Skr(Manacher马拉车+Hash哈希/回文树)

题目

给一个只由数字构成的字符串s(|s|<=2e6)

求s的所有本质不同回文串之和%1e9+7,

每一个回文串的贡献为其十进制下的值

思路来源

字符串hash_短hash-CSDN博客(Hash相关)

计蒜客 2018南京网络赛 I Skr(马拉车+哈希)_i. skr-CSDN博客

ACM-ICPC 2018 南京赛区网络预赛 I. Skr (马拉车+字符串hash/回文自动机)_acm-icpc 2018 南京赛区网络预赛 skr-CSDN博客

Skr(马拉车+哈希)_a number is skr, if and only if it's unchanged aft-CSDN博客

题解

考虑到每个回文串都会在Manacher的过程中被遍历到,

那么我们每得到一个回文串,就判断该回文串是否已经被加入Hash里了即可

已经加入了就不计入其贡献,否则将其加入Hash并统计其贡献

P(这里取2e6+7)进制下mod2^64与10进制下mod(1e9+7)的搭配,冲突性很小,能卡过

子串Hash值小技巧:利用前缀和,[1,r]与[1,l-1]左对齐,求[l,r]的Hash值

子串Sum值小技巧同上,都是之前没有好好留意的写法

然后Hash用的就是建图那里的链式前向星类似的写法,开三个数组,开散列链表Hash

后续:

学了回文树(又名回文自动机),发现这玩意比后缀自动机简单多了……

所以就是第一次插入本质不同回文串,也就是新创建节点的时候,加上那个数的贡献即可……

心得

魔改Manacher,之前一直写Manacher的板子题

魔改Hash,之前一直只总结Hash的板子

Hash还是比较正常,利用了一个前缀和对其作差求子串的Hash值

剩下的就是p进制下的开散列,与链式前向星一致

Manacher相关下标还是比较重要的啊,好好debug

代码1(Manacher+Hash)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=2e6+10;
const int p=2e6+7;//p进制下 Hash链式前向星 开散列  
const int mod=1e9+7;
char w[maxn],t[maxn<<1];
int R[maxn<<1],tot;
//R[i]: 以i为中心的包含i和井号的最长回文半径 减1为回文串长 

int head[maxn<<1],Next[maxn<<1],cnt;//链式 
ull link[maxn<<1];//该点是否存在映射值 

ull base[maxn<<1],Hash[maxn<<1];//p进制下 mod(2^64) Hash值的前缀和 
ll base10[maxn<<1],sum[maxn<<1];//10进制下 mod(1e9+7) 数字值的前缀和  

ll res;
bool add(ull x)
{
	ull u=x%p;//P为hash的模数 
	for(int i=head[u];i;i=Next[i])
	if(link[i]==x)return 1;
	Next[++cnt]=head[u];
	head[u]=cnt;
	link[cnt]=x;
	return 0;
}
ll solve(int l,int r)
{
	ull x=Hash[r]-Hash[l-1]*base[r-l+1];//[l,r]的Hash值 利用前缀和 头对齐之后作差得Hash值 
	if(add(x))return 0;
	ll res=(sum[r]-sum[l-1]*base10[(r-l+2)/2]%mod+mod)%mod;//答案只在mod(1e9+7)下进行 
	//(r-l+1)/2是串长的一半向下取整 [(r-l+1)+1]/2是串长的一半向上取整
	//对于一个1#2#1的串,其数字的数量应为串长一半向上取整 
	return res;
}
int init1(char w[],char t[])//原串:w 新串:t  
{
	tot=0;
	memset(t,0,sizeof t);
	memset(R,0,sizeof R);
	int len=strlen(w);
	t[0]='@';
	for(int i=0;i<len;++i)
	{
		t[++tot]='#';
		t[++tot]=w[i];
	}
	t[++tot]='#';
	t[++tot]='*';
	return 2*len+1;
	//新串长度 为#3#2#3#的长度 不包括@和*
	//新串下标为 [1,2*len+1] 
}
void init2(char t[],int len)
{
	cnt=0;
	memset(head,0,sizeof head);
	memset(Next,0,sizeof Next);
	memset(link,0,sizeof link);
	base[0]=base10[0]=1;
	sum[0]=Hash[0]=0;
	for(int i=1;i<=len;++i)
	{
		base[i]=base[i-1]*p;//p进制下 mod(2^64)
		base10[i]=base10[i-1]*10%mod;//10进制下 mod(1e9+7) 
		Hash[i]=Hash[i-1]*p+t[i];
		if(t[i]>='0'&&t[i]<='9')sum[i]=(sum[i-1]*10+t[i]-'0')%mod;
		else sum[i]=sum[i-1]; 
	} 
} 
void manacher(char t[],int len)
{
	int r=0,pos=0;
	//int ans=0;
	res=0;
	for(int i=1;i<=len;++i)
	{
		if(t[i]!='#')(res+=solve(i,i))%=mod;//[i,i]的贡献
		if(r>i)R[i]=min(r-i,R[2*pos-i]);
		else R[i]=1;
		while(t[i-R[i]]==t[i+R[i]])
		{
			if(t[i+R[i]]!='#')(res+=solve(i-R[i],i+R[i]))%=mod;//[i-R[i],i+R[i]]的贡献
			R[i]++;
		}
		if(R[i]+i>r)
		{
		 pos=i;//最右位置对应的回文中心 
		 r=R[i]+i;//最右位置 
	    }
		//ans=max(ans,R[i]);
	}
	//return ans-1;
}
int main()
{
	while(~scanf("%s",w))
    {
    	int a=init1(w,t);
    	init2(t,a);
    	manacher(t,a);
    	printf("%lld\n",res);
    }
	return 0;
}

代码2(回文树)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2e6+5 ;
const int N = 10 ;
const int mod=1e9+7;
char s[MAXN];
int len;
ll ten[MAXN],ans;//ten[i]表示1后面i个0 

struct Palindromic_Tree {
	int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
	int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
	int cnt[MAXN] ;//cnt[i]表示编号为i的节点表示的回文字符串在整个字符串中出现了多少次。
	//cnt[i]建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的
	int num[MAXN] ;//num[i]表示节点i表示的回文字符串中,有多少个本质不同的字符串(包括本身) 
	ll v[MAXN];//每个点代表的实际十进制的值 
	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 ) {//和KMP一样,失配后找一个尽量最长的
		while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
		return x ;
	}
 
	void add ( int c ) {
		c -= '0'; 
		S[++ n] = c ;
		int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
		if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
			int now = newnode ( len[cur] + 2 ) ;//新建节点
			if(len[now]==1)v[now]=c; 
			else if(len[now]==2)v[now]=ten[1]*c+c;
			else v[now]=((c*ten[len[cur]]+v[cur])*ten[1]+c)%mod;//前后各补一个c 
			ans=(ans+v[now])%mod;
			fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
			next[cur][c] = now ;
			num[now] = num[fail[now]] + 1 ;
		}
		last = next[cur][c] ;
		cnt[last] ++ ;
	}
 
	void count()//如果不count向上汇总 每个节点代表的回文串 当且仅当完全相同时才会被记次数 
	{
		for ( int i = p - 1 ; i >= 0 ; -- i ) 
		cnt[fail[i]] += cnt[i] ;
		//fail[i]累加i的cnt,因为如果fail[v]=u,则u一定是套在v内的子回文串!
		//所以 u自己出现了一次 在v中出现了一次 归回去能统计出u出现的次数 
	}
}a;
int main()
{
	ten[0]=1;
	for(int i=1;i<MAXN;++i)
	ten[i]=(ten[i-1]*10)%mod;
	a.init();
	scanf("%s",s);
	len=strlen(s); 
	for(int i=0;i<len;++i)
	a.add(s[i]);
	printf("%d\n",ans);
	return 0;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值