P8256动规题解

首先,我们看 1 0 9 + 7 10^9+7 109+7这个数,它已经十分接近 i n t int int的最大数,所以要知道应该用 l o n g long long l o n g long long做。

接下来,我们来模拟一下样例1的第一组数据:

10-01-

遇到’-'那么删掉开头的

001-

遇到’-',再删掉开头的

01,这是第一种

那第二种方法就不再赘述了


我们再来看一组数据吧

S:111–00

T:100

首先看第一个’-',可以删结尾,也可以删开头,有两种可能

在来看第二个’-',与第一个一样,也有两种可能。

所以,一共有 2 ∗ 2 = 4 2*2=4 22=4种可能

也就是说,多加一个’-',方案数就乘2(可能包含不能让Kri开心的)

我们设’-'的数量为t

所以方案数就是 2 t 2^t 2t

那么我们看数据,一共有400个字符,所以可能会有上百个’-'那就是 2 100 2^{100} 2100种方案,枚举出来所有可能就只能拿部分分。所以我们要用DP计数了。


我们再定义一个R串

R: k k k j j j l l l

k k k:开头要删除的字符个数

j j j:已经与部分T串匹配好的字符个数

l l l:结尾要删除的字符个数

那我们假设一下 l l l后面还有一个 j j j

R : k k k j j j l l l j j j

那我们由于 j j j是匹配过的,就不能动,那怎么删 l l l,很明显,不能删,所以不能再 l l l后面添加东西。

好了,根据我们之前的归纳,我们知道有两个’-'有四种解法。

分别是:

尾 尾

头 头

头 尾

尾 头

那么我们把这四种情况带入到R串看看是什么情况

  1. R : k k k j j j l l l

        1 11  
    
  2. R : k k k j j j l l l

     11 1 
    
  3. R : k k k j j j l l l

      1 1 1
    
  4. R : k k k j j j l l l

    1 1 1       
    

接下来我们来定义f数组

f[i][j][k][l]:判断到第i个字符(没用上),已经匹配了j个字符,开头要删除k个字符,末尾要删除l个字符。

初始状态:f[1][0][0][0]

  1. f[2][1][0][0]->f[3][1][0][1]->f[4][1][0][2]
  2. f[2][0][1][0]->f[3][0][2][0]->f[4][1][2][0]
  3. f[2][0][1][0]->f[3][1][1][0]->f[4][1][1][1]
  4. f[2][0][1][0]->f[3][1][1][0]->f[4][1][1][1]

第四组与第三组一样

f[i][j][k][l]->f[i+1][j+1][k][l] 匹配

f[i][j][k][l]->f[i+1][j][k][l+1] 没有匹配,放入 l l l

f[i][j][k][l]->f[i+1][j][k+1][l] 没有匹配,放入 k k k这个东西我们可以求出来,是 2 t 2^t 2t

f[i][j][k][l]->f[i+1][j][k-1][l] 遇到减号,删左边

f[i][j][k][k]->f[i+1][j][k][l-1] 遇到减号,删右边


那么如果我们用四维数组,对上数据,得算 40 0 4 400^4 4004

但是我们的 l l l却是可以求出来的:是 i − 1 − 2 t − j − k i-1-2t-j-k i12tjk

那么就可以降一维了


#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 405, MOD = 1e9+7;

ll f[N][N][N],sum[N];
char s[N],t[N];

ll q_pow(ll a,ll b)//快速幂
{
    ll ans=1;
    while(b)
    {
        if(b&1)
            ans=ans*a%MOD;
        a=a*a%MOD;
        b/=2;
    }
    return ans;
}
int main()
{
    int q,n,m,i,j,k;
    cin >> q;
    while(q--)
    {
        scanf("%d%d%s%s", &n, &m, s+1, t);
        for (int i = 1; i <= n; i ++ )
        {
            if(s[i]=='-')
                sum[i]=sum[i-1]+1;
            else
                sum[i]=sum[i-1];
        }
        memset(f, 0, sizeof f);
        for (int i = 1; i <= n; i ++ )//判断s串的第i个字符
        {
            for(int j = 0; j <= min(i,m);j++)//T串的前j个字符已经匹配
            {
                for(k = 0; k < i; k ++ )//R串开头的k个字符需删除
                {
                    int l=i-1-k-j-sum[i-1]*2;//R串末尾的l个字符需要删除
                    if(l<0) break;
                    if(j == 0 && l == 0) //R串只有前k个要删除的字符,此时方法数=2^(sum[i-1]),有一个'-'就有两种方法
                        f[i][0][k]=q_pow(2,sum[i-1]);//第一次f[1][0][0]=1
                    if(s[i]!='-')
                    {
                        f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%MOD;//不匹配
                        if(j!=m && s[i]==t[j] && l==0)//匹配
                            f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k])%MOD;
                    }
                    else//是'-'要么删左边,要么删右边
                    {
                        if(k)//删左边,左边是连续的
                            f[i+1][j][k-1]=(f[i+1][j][k-1]+f[i][j][k])%MOD;
                        if(l)//删右边,右边是连续的
                            f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%MOD;
                    }
                }
            }
        }
        printf("%lld\n",f[n+1][m][0]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值