洛谷题单指南-动态规划2-P2679 [NOIP2015 提高组] 子串

原题链接:https://www.luogu.com.cn/problem/P2679

题意解读:在a中按顺序挑选k个子串,使得这些子串连在一起正好和b相等,求方案数。

解题思路:

这样的题目,无外乎两个思路:DFS暴搜(得部分分)、动态规划

动态规划不好想,还是先暴搜吧!

1、DFS暴搜

搜索的思路就是:从a起始位置开始,一直找到跟b前缀相等的字符,然后枚举连续有多少个字符相等,都作为一种可能方案,

再把相等的前缀拿掉,相应子串数量+1,对a、b剩下的部分继续进行dfs,

当然,为了尽可能时间最优,还需要进行合理的剪枝:

剪枝操作1:如果已经累计找到超过k个子串了还没有匹配上,就不用再继续了

剪枝操作2:如果a剩下的长度比b剩下的长度小,也不用再继续判断了

40分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1005, M = 205, MOD = 1000000007;
string a, b;
int n, m, k, ans;

//从a的ai开始,枚举子串和b的bi开始的前缀匹配,匹配上数量cnt+1
void dfs(int ai, int bi, int cnt)
{
    if(cnt == k && bi == b.size())
    {
        ans = (ans + 1) % MOD;
        return;
    }
    if(cnt >= k) return; //剪枝:超过k个子串,不需要再继续处理
    if(a.size() - ai < b.size() - bi) return; //剪枝:a剩下的长度比b剩下的小,a的子串永远不能等于b
    for(int i = ai; i < a.size(); i++)
    {
        if(a[i] == b[bi]) //如果找到a、b相等的位置
        {
            int j = 0;
            while(i + j < a.size() && bi + j < b.size() && a[i + j] == b[bi + j]) //往后枚举所有相等的前缀,作为一种方案,并继续dfs
            {
                dfs(i + j + 1, bi + j + 1, cnt + 1);
                j++;
            }
        }
    }
}

int main()
{
    cin >> n >> m >> k >> a >> b;
    dfs(0, 0, 0);
    cout << ans;
    return 0;
}

2、动态规划

首先,思考状态表示,

本题直观上需要三个状态:以a[i]为终点,与b[1~j]匹配,且子串数为p的总方案数,

但是,由于不是每一个a[i]为终点都能和b[1~j]匹配,要匹配必须有a[i] = b[j],

所以对于每一个i,a[i]这个终点可以用也可以不用,干脆定义两个状态:

f[i][j][p]:表示以a[i]为终点(a[i]使用),和b[1~j]匹配,且子串数是p的方案数

g[i][j][p]:表示以a[i]为终点(a[i]不使用),和b[1~j]匹配,且子串数是p的方案数

状态转移为:

当a[i] == b[j]时,可以使用a[i]作为终点,也可以不使用a[i]作为终点,f,g都要转移

  f[i][j][p] = f[i-1][j-1][p-1] + g[i-1][j-1][p-1] + f[i-1][j-1][p],

    使用a[i]作为终点的方案 = a[i]单独作为子串的方案 + a[i]跟前面一起作为子串的方案

    a[i]单独作为子串的方案 = a[i-1]作为终点的方案 + a[i-1]不作为终点的方案 = f[i-1][j-1][p-1] + g[i-1][j-1][p-1]

    a[i]跟前面一起作为子串的方案 = a[i-1]作为终点的方案 =  f[i-1][j-1][p],注意这里还是p,而不是p-1,因为a[i]没有单独成一个子串    

  g[i][j][p]  = f[i-1][j][p] + g[i-1][j][p],

    不使用a[i]作为终点的方案 = 使用a[i-1]作为终点的方案 + 不使用a[i-1]作为终点的方案

当a[i] != b[j]时,只能不使用a[i]作为终点,g需要转移,f对应的方案是0

  f[i][j][p] = 0,

    因为a[i] != b[j],所以使用a[i]作为终点来匹配b[1~j]的方案数是0

  g[i][j][p] = f[i-1][j][p] + g[i-1][j][p],

    不使用a[i]作为终点的方案 = 使用a[i-1]作为终点的方案 + 不使用a[i-1]作为终点的方案

初始化:f[i][0][0] = 1, i从0~n,以i为终点、和空串匹配、选0个子串的方案是1

答案就是f[n][m][k] + g[n][m][k]

注意:空间复杂度是n * m * k * 4 * 2 = 1000 * 200 * 200 * 4 * 2 = 320M,肯定是不行的

但从递推式来看i只依赖i-1,可以用优化掉一维,先写出三维的代码,只针对1~7数据点,n=500,m=50

70分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 505, M = 55, MOD = 1000000007;
string a, b;
int n, m, k;
int f[N][M][M], g[N][M][M];

int main()
{
    cin >> n >> m >> k >> a >> b;
    a = " " + a; //使字符串从1开始
    b = " " + b; //使字符串从1开始
    f[0][0][0] = 1;
    for(int i = 1; i <= n; i++)
    {
        f[i][0][0] = 1;
        for(int j = 1; j <= m; j++)
        {
            for(int p = 1; p <= k; p++)
            {
                if(a[i] == b[j])
                {
                    f[i][j][p] = ((f[i-1][j-1][p-1] + g[i-1][j-1][p-1]) % MOD + f[i-1][j-1][p]) % MOD;
                    g[i][j][p] = (f[i-1][j][p] + g[i-1][j][p]) % MOD;
                }
                else
                {
                    f[i][j][p] = 0;
                    g[i][j][p] = (f[i-1][j][p] + g[i-1][j][p]) % MOD;
                }
            }
        }
    }
    cout << (f[n][m][k] + g[n][m][k]) % MOD;
    return 0;
}

再基于70代码进行空间优化

首先,将N、M定义为1005, 205

其次,由于递推关系中i只依赖i-1,可以将f[N][M][M],g[N][M][M]的第一维用滚动数组优化:f[2][M][M],g[2][M][M]

最后,定义now,old变量,每次i枚举时交换位置,达到0/1交替滚动的效果

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1005, M = 205, MOD = 1000000007;
string a, b;
int n, m, k;
int f[2][M][M], g[2][M][M];

int main()
{
    cin >> n >> m >> k >> a >> b;
    a = " " + a; //使字符串从1开始
    b = " " + b; //使字符串从1开始
    f[0][0][0] = f[1][0][0] = 1;
    int now = 0, old = 1; //滚动数组
    for(int i = 1; i <= n; i++)
    {
        swap(now, old); //交替滚动
        for(int j = 1; j <= m; j++)
        {
            for(int p = 1; p <= k; p++)
            {
                if(a[i] == b[j])
                {
                    f[now][j][p] = ((f[old][j-1][p-1] + g[old][j-1][p-1]) % MOD + f[old][j-1][p]) % MOD;
                    g[now][j][p] = (f[old][j][p] + g[old][j][p]) % MOD;
                }
                else
                {
                    f[now][j][p] = 0;
                    g[now][j][p] = (f[old][j][p] + g[old][j][p]) % MOD;
                }
            }
        }
    }
    cout << (f[now][m][k] + g[now][m][k]) % MOD;
    return 0;
}

 

posted @ 2024-04-26 16:52  五月江城  阅读(34)  评论(0编辑  收藏  举报