【题解】回文匹配

题目传送门:【洛谷】回文匹配

回文匹配

题目描述

对于一对字符串 \((s_1,s_2)\),若 \(s_1\) 的长度为奇数的子串 \((l,r)\) 满足 \((l,r)\) 是回文的,那么 \(s_1\) 的“分数”会增加 \(s_2\)\((l,r)\) 中出现的次数。

现在给出一对 \((s_1,s_2)\),请计算出 \(s_1\) 的“分数”。

答案对 \(2 ^ {32}\) 取模。

输入格式

第一行两个整数,\(n,m\),表示 \(s_1\) 的长度和 \(s_2\) 的长度。

第二行两个字符串,\(s_1,s_2\)

输出格式

一行一个整数,表示 \(s_1\) 的分数。

样例 #1

样例输入 #1

10 2
ccbccbbcbb bc

样例输出 #1

4

样例 #2

样例输入 #2

20 2
cbcaacabcbacbbabacca ba

样例输出 #2

4

提示

【样例解释】

对于样例一:

子串 \((1,5)\)\(s_2\) 出现了一次,子串 \((2,4)\)\(s_2\) 出现了一次。

子串 \((7,9)\)\(s_2\) 出现了一次,子串 \((6,10)\)\(s_2\) 出现了一次。


【数据范围】

本题采用捆绑测试。

  • 对于 \(100\%\) 的数据:\(1 \le n,m \le 3 \times 10 ^ 6\),字符串中的字符都是小写字母。

  • 详细的数据范围:

    Subtask 编号 \(n,m \le\) 分值
    \(1\) \(100\) \(15\)
    \(2\) \(10 ^ 3\) \(15\)
    \(3\) \(5 \times 10 ^ 3\) \(20\)
    \(4\) \(4 \times 10 ^ 5\) \(30\)
    \(5\) \(3 \times 10 ^ 6\) \(20\)

算法1:

有贡献的子串的左端标记1,每次找最大的回文,在左端能遍历的范围内,计算离两边端哪个最近,其距离即贡献值。
\(\sum \limits_{i=l}^{r}\)\(a_i\times(i-l+1)+\sum \limits_{i=l}^{r}a_i\times(r-i+1)\)

通过观察我们只需要维护\(a_i和a_i\times i\)即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 3100010;
int n,m,val[N],p[N],ne[N],sum[N],summ[N];
char s[N],str[N],pp[N];

void init()
{
	for(int i=2,j=0;i<=m;i++)
	{
		while(j&&pp[i]!=pp[j+1]) j=ne[j];
		if(pp[i]==pp[j+1]) j++;	
		ne[i]=j;
	}
}
void kmp()
{
	
	for(int i=1,j=0;i<=n;i++)
	{
		while(j&&s[i]!=pp[j+1]) j=ne[j];
		if(s[i]==pp[j+1]) j++;
		if(j==m)
		{
			j=ne[j];
			val[i-m+1]=1;
		}
	}
}
void manacher()
{
	int mid=0,mr=0;
	for(int i=1;i<=n;i++)
	{
		if(i<mr) p[i]=min(p[mid*2-i],mr-i);
		else p[i]=0;
		while(s[i+p[i]+1]==s[i-p[i]-1]&&i-p[i]-1>=1&&i+p[i]+1<=n)
		{
		    ++p[i];
		} 
		if(i+p[i]>mr)
		{
		    mr=i+p[i];
		    mid=i;
		}
 	}
}
void solve()
{
	long long ans=0;
	for(int i=1;i<=n;i++) 
	{
	    sum[i]=sum[i-1]+val[i];
	    summ[i]=summ[i-1]+val[i]*i;	
	}
	int mid,l,r;
	for(int i=1;i<=n;i++)
	{
		l=i-p[i],r=i+p[i]-m+1;
		if(l>r) continue;
		mid=(l+r)>>1;
		ans+=summ[mid]-summ[l-1]-(sum[mid]-sum[l-1])*(l-1);
	
		if(mid!=r) ans+=(sum[r]-sum[mid])*(r+1)- (summ[r]-summ[mid]);//如果在后半段
		
	}
	long long  mod=pow(2,32);
        printf("%lld\n",ans%mod);	
}

int main()
{
	cin>>n>>m;
	cin>>s+1>>pp+1;
	init();
	manacher();

	kmp();
	solve();
	
	
	return 0;
}

算法2:
双重前缀和

posted @ 2022-10-14 14:54  watasky  阅读(15)  评论(0编辑  收藏  举报