2018-2019 ACM-ICPC南京 M. Mediocre String Problem(SAM+PAM)

Posted issue是fw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2018-2019 ACM-ICPC南京 M. Mediocre String Problem(SAM+PAM)相关的知识,希望对你有一定的参考价值。

LINK

题意

给定串 s s s和串 t t t

要求在 s s s中找一个子串 s ′ s' s(不需要本质不同), t t t中找一个前缀 t ′ t' t

满足 s ′ s' s长度大于 t ′ t' t,使得 s ′ + t ′ s'+t' s+t是一个回文串,求方案数


好像这题用 e x k m p + m a n c h e r exkmp+mancher exkmp+mancher就是裸题??

update:我是个憨批,用后缀数组的话更是裸题

因为我们实际上只需要知道 s s s的每个前缀的反串和 t t t l c p lcp lcp长度

于是可以对 s s s的反串和 t t t连在一起跑后缀数组,套个 R M Q RMQ RMQ就能求 l c p lcp lcp


因为 s ′ s' s长度大于等于 t ′ t' t

所以 s ′ s' s可以分为两部分,即 s ′ = s 1 ′ + s 2 ′ s'=s_1'+s_2' s=s1+s2

其中 s 1 ′ s_1' s1 t ′ t' t的反串, s 2 ′ s_2' s2是一个回文串

那么我们可以枚举串 s s s的位置 i i i,让 i i i变成 s 1 ′ s_1' s1的结束位置即可

于是问题转化为求出

①. i + 1 i+1 i+1开头的回文串个数

②.以 i i i结尾并等于 t t t串某个前缀的子串个数

首先①很好求,对反串建回文树即可

②的话,考虑对 s s s建立回文自动机

存下 s s s每个前缀 s [ 1... i ] s[1...i] s[1...i]的节点记作 I D [ i ] ID[i] ID[i]

然后把反串 t t t放在 S A M SAM SAM上跑,最后我们会得到一个节点 p p p和匹配长度 L L L

于是 s [ 1... L ] s[1...L] s[1...L]在节点 p p p中出现过一次

我们还想知道 s [ 1... L − 1 ] s[1...L-1] s[1...L1]在哪些节点中出现过

因为刚才我们是插入的反串,所以现在相当于删除匹配的第一个字母

S A M SAM SAM是支持删除的,直接让 L − − L-- L,然后跳后缀链接即可

最后因为一个串在父亲节点出现也会在儿子节点出现,所以还需要来一遍dfs/拓扑

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e6+10;
int l[maxn],fa[maxn],zi[maxn][30],id=1,las=1,ID[maxn];
char s[maxn],t[maxn];
void insert(int c)
{
	int p = las,np = ++id; las = id;
	l[np] = l[p]+1; ID[l[np]] = np;
	for( ; p&&zi[p][c]==0 ;p=fa[p] )	zi[p][c] = np;
	if( !p )	fa[np] = 1;
	else
	{
		int q = zi[p][c];
		if( l[q]==l[p]+1 )	fa[np] = q;
		else
		{
			int nq = ++id; fa[nq] = fa[q],l[nq] = l[p]+1;
			memcpy( zi[nq],zi[q],sizeof zi[nq] );
			fa[np] = fa[q] = nq;
			for( ; p&&zi[p][c]==q ;p=fa[p] )	zi[p][c] = nq;	
		}
	}
}
int rk[maxn],c[maxn],f[maxn],h[maxn];
vector<int>vec[maxn];
void dfs(int u)
{
	for(auto v:vec[u] )	
		f[v] += f[u], dfs(v);
}
void chiken_sort()
{
	for(int i=2;i<=id;i++)	vec[fa[i]].push_back( i );
	dfs(1);
}

struct PAM
{
	int zi[maxn][30],fail[maxn],l[maxn],id,las,cnt[maxn];
	PAM()
	{
		fail[0] = 1, fail[1] = 1, l[0] = 0, l[1] = -1;
		las = id = 1;
	}
	int get_fail(int u,int index)
	{
		while( s[index]!=s[index-l[u]-1] )	u = fail[u];
		return u;
	}
	void insert(int c,int index)
	{
		int u = get_fail(las,index);
		if( zi[u][c]==0 )
		{
			int now = ++id, v = get_fail(fail[u],index);
			l[now] = l[u]+2; 
			fail[now] = zi[v][c]; zi[u][c] = now;
			cnt[now] = cnt[fail[now]]+1;
		}
		las = zi[u][c];
	}
}pam;
int main()
{
	scanf("%s%s",s+1,t+1);
	int n = strlen( s+1 ),m = strlen( t+1 );
	for(int i=1;i<=n;i++)	insert( s[i]-'a' );
	
	int p = 1, L = 0;
	for(int i=m;i>=1;i--)
	{
		int c = t[i]-'a';
		if( zi[p][c] )	p = zi[p][c],L++;
		else
		{
			while( p && !zi[p][c] )	p = fa[p];
			if( !p )	p = 1, L = 0;
			else	L = l[p]+1, p = zi[p][c];	
		}
	}
	while( L )
	{
		f[p]++; L--;
		if( L==l[fa[p]] )	p = fa[p];
	}
	chiken_sort();
	
	reverse(s+1,s+1+n);
	for(int i=1;i<=n;i++)
	{
		pam.insert( s[i]-'a',i );
		h[n-i+1] = pam.cnt[pam.las];
	}
	long long ans = 0;
	for(int i=1;i<=n;i++)
		ans += 1ll*f[ID[i]]*h[i+1]; 
	cout << ans;
}

也可以换一种 S A M SAM SAM写法

直接拿反串 t t t s s s的自动机上跑到 p p p节点,匹配长度 L L L

考虑对长度为 1 , 2... L 1,2...L 1,2...L的前缀 t t t串都算一次贡献,这些串我们只需要跳后缀链接就可以得到

比如在当前在节点 p p p,那么我们需要对长度为 l [ f a p ] + 1... l [ p ] l[fa_p]+1...l[p] l[fap]+1...l[p]的这些 t t t前缀都算一次贡献

贡献就是这些串后的回文个数和

我们在创建 S A M SAM 2018ACM-ICPC南京区域赛M---Mediocre String ProblemexKMPManacher

2018 ACM-ICPC 南京站 M.Mediocre String Problem 回文树+二分hash/拓展kmp

M. Monster Hunter——2020南京

2018 ACM-ICPC南京区域赛题解

ACM-ICPC 2018 南京赛区网络预赛 L. Magical Girl Haze(分层dijkstra)

ACM-ICPC 2017 沈阳赛区现场赛 M. Wandering Robots && HDU 6229