子序列DP、哈希、回文——K-Strings, Subsequences, Reversed Subsequences, Prefixes

【题面】

在这里插入图片描述

【题目大意】

  • 给定两个字符串s和t,求出在s里面有多少个本质不同的子序列,使得该序列的前缀包含t,且该序列的反串也包含t
  • 即 s的子序列=t+x+反t

【解题思路】

  • 求x有多少个本质不同的子序列求t有多少个回文后缀
  • 根据子序列的定义,先将s包含t的最短前缀序列和反t的最短后缀序列长度求出来,这样的前缀和后缀就可以先满足题意了
  • 然后可以分成两种答案统计情况:
  1. 要求s的前缀和后缀不重叠,也就是x非空。这样x可以任选,问题就转化成:求x有多少个本质不同的子序列。该问题是一个DP的问题
    • f [ i ] f[i] f[i]表示前i个位置,本质不同的子序列的个数。
    • 对于字符i,可以分成两种情况:
      • 如果该字符未在之前出现过,那么就是直接拼接在前一个字符串的后面,即: f [ i ] = f [ i − 1 ] × 2 f[i]=f[i-1]\times2 f[i]=f[i1]×2
      • 如果该字符出现过,那么在拼接完之后,要去掉重复的部分。假设该字符上一次出现的位置是 l s t [ s [ i ] ] lst[s[i]] lst[s[i]],那么就要去掉以该位置的“槽位”所形成的方案数,也就是 f [ l s t [ s [ i ] ] − 1 ] f[lst[s[i]]-1] f[lst[s[i]]1]。综上, f [ i ] = f [ i − 1 ] × 2 − f [ l s t [ s [ i ] ] − 1 ] f[i]=f[i-1]\times2-f[lst[s[i]]-1] f[i]=f[i1]×2f[lst[s[i]]1]
    • 最终,答案是 f [ n ] f[n] f[n]
  2. 要求s的前缀和后缀重叠,也就是x为空。可以观察发现,这样组成的子序列是回文的,因为变成了 t和反t 的一部分结合,自然就是回文的。
    • 进一步观察,可以发现 t和反t 的重叠发生在t的后缀,也就是要让t的后缀回文。满足这个条件后,拼接起来就可以满足整体回文了
    • 因此,问题就转化成:求t有多少个回文后缀。这个问题可以用哈希解决。
    • 用一个正向哈希值和反向哈希值,枚举后缀的左端点,然后判断正向和反向哈希值是否相同即可。
    • 同时,也要判断拼接是否可行,利用此前求出的前缀长度和后缀长度数组,只要在前缀的位置在后缀的前面即可。
  • 将以上情况的答案加起来,就是最终结果了。

【参考程序】

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
#define int LL
#define Mod 1000000007

int n,m,ans,
    pre[1000005],suf[1000005],f[1000005],lst[1000005];
ULL h1[1000005],h2[1000005],p[1000005];
char s[1000005],t[1000005];
signed main()
{
    cin>>n>>m;
    scanf("%s%s",s+1,t+1);
    // printf("%s\n%s\n",s+1,t+1);
    for (int i=1,j=1;i<=n&&j<=m;i++)
    {
        if (s[i]==t[j])
        {
            pre[j]=i;
            j++;
        }
    }
    for (int i=n,j=1;i>=1&&j<=m;i--)
    {
        if (s[i]==t[j])
        {
            suf[j]=i;
            j++;
        }
    }
    if (!pre[m]||!suf[m])
    {
        printf("0");return 0;
    }

    if (pre[m]<suf[m])
    {
        f[pre[m]]=1;
        for (int i=pre[m]+1;i<suf[m];i++)
        {
            if (!lst[s[i]])
                f[i]=(f[i-1]*2)%Mod;
            else   
                f[i]=(f[i-1]*2-f[lst[s[i]]-1]+Mod)%Mod;
            lst[s[i]]=i;
        }
        ans+=f[suf[m]-1];
    }

    for (int i=1;i<=m;i++)
        h1[i]=h1[i-1]*37+(t[i]-'a');
    for (int i=1;i<=m;i++)
        h2[i]=h2[i-1]*37+(t[m-i+1]-'a');
    p[0]=1;
    for (int i=1;i<=m;i++)
        p[i]=p[i-1]*37;
    for (int i=1;i<=m;i++)
    {
        ULL ha=h1[m]-h1[m-i]*p[i],
            hb=h2[i];
        if (ha==hb)
        {
            if (pre[m-i]<suf[m])
                ans=(ans+1)%Mod;
        }
    }
    cout<<ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值