2018-2019 ACM-ICPC南京 M. Mediocre String Problem(SAM+PAM)
发布日期:2021-06-30 10:33:09 浏览次数:2 分类:技术文章

本文共 3381 字,大约阅读时间需要 11 分钟。

题意

给定串 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 
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
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 SAM的时候给新建节点 n p np np一个权值

因为 n p np np节点代表的是 s [ 1... i ] s[1...i] s[1...i],所以它的权值赋为 i + 1 i+1 i+1开头的回文串个数

这样我们最后对 S A M SAM SAM做一遍dfs/拓扑就可以得到节点内每个串在原串 s s s中作为不同的 e n d p o s endpos endpos时的 i + 1 i+1 i+1开头的回文串个数和

这样就可以统计啦!!

感觉比我上面的做法要难理解

转载地址:https://issue-is-vegetable.blog.csdn.net/article/details/116713405 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:AT2395 [ARC071C] TrBBnsformBBtion(构造)
下一篇:45届ICPC亚洲区域赛(上海)C.Sum of Log(卡常数位dp)

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月26日 09时20分12秒