题解:洛谷 P2679 [NOIP 2015 提高组] 子串

【题目来源】

洛谷:P2679 [NOIP 2015 提高组] 子串 - 洛谷

【题目描述】

有两个仅包含小写英文字母的字符串 \(A\)\(B\)

现在要从字符串 \(A\) 中取出 \(k\) 个互不重叠的非空子串,然后把这 \(k\) 个子串按照其在字符串 \(A\) 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 \(B\) 相等?

注意:子串取出的位置不同也认为是不同的方案。

【输入】

第一行是三个正整数 \(n,m,k\),分别表示字符串 \(A\) 的长度,字符串 \(B\) 的长度,以及问题描述中所提到的 \(k\),每两个整数之间用一个空格隔开。

第二行包含一个长度为 \(n\) 的字符串,表示字符串 \(A\)

第三行包含一个长度为 \(m\) 的字符串,表示字符串 \(B\)

【输出】

一个整数,表示所求方案数。

由于答案可能很大,所以这里要求输出答案对 \(1000000007\) 取模的结果。

【输入样例】

6 3 1 
aabaab 
aab

【输出样例】

2

【解题思路】

image

【算法标签】

《洛谷 P2679 子串》#字符串# #动态规划,dp# #枚举# #NOIP提高组# #O2优化# #2015#

【代码详解】

// 70分版本
#include <bits/stdc++.h>
using namespace std;

#define int long long  // 定义int为long long类型
const int MOD = 1000000007;  // 定义模数

// 四维DP数组:
// dp[i][j][k][0] - 前i个s1和前j个s2使用k次匹配且不以s1[i]结尾的方案数
// dp[i][j][k][1] - 前i个s1和前j个s2使用k次匹配且以s1[i]结尾的方案数
int dp[505][55][55][2];  

int n, m, x;        // n: s1长度, m: s2长度, x: 最大匹配次数
string s1, s2;      // 输入的两个字符串

signed main()
{
    // 输入参数和字符串
    cin >> n >> m >> x >> s1 >> s2;
  
    // 在字符串前添加空格,使索引从1开始
    s1 = " " + s1;
    s2 = " " + s2;
  
    // 初始化DP数组:空字符串有1种匹配方式
    for (int i = 0; i <= n; i++)
    {
        dp[i][0][0][0] = 1;
    }
  
    // 动态规划填表
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            for (int k = 1; k <= x; k++)
            {
                // 不选择s1[i]作为匹配结尾的情况
                dp[i][j][k][0] = (dp[i-1][j][k][0] + dp[i-1][j][k][1]) % MOD;
              
                // 选择s1[i]作为匹配结尾的情况
                if (s1[i] == s2[j])
                {
                    dp[i][j][k][1] = (dp[i-1][j-1][k][1] + 
                                     dp[i-1][j-1][k-1][1] + 
                                     dp[i-1][j-1][k-1][0]) % MOD;
                }
                else
                {
                    dp[i][j][k][1] = 0;  // 字符不匹配,方案数为0
                }
            }
        }
    }
  
    // 输出结果:总方案数取模
    cout << (dp[n][m][x][0] + dp[n][m][x][1]) % MOD;
  
    return 0;
}
// 100分版本,使用滚动数组
#include <bits/stdc++.h>
using namespace std;

#define int long long  // 定义int为long long类型
const int MOD = 1000000007;  // 定义模数

// 滚动数组优化后的四维DP数组:
// dp[0/1][j][k][0] - 前i个s1和前j个s2使用k次匹配且不以s1[i]结尾的方案数
// dp[0/1][j][k][1] - 前i个s1和前j个s2使用k次匹配且以s1[i]结尾的方案数
int dp[2][205][205][2];  

int n, m, x;        // n: s1长度, m: s2长度, x: 最大匹配次数
string s1, s2;      // 输入的两个字符串

signed main()
{
    // 输入参数和字符串
    cin >> n >> m >> x >> s1 >> s2;
  
    // 在字符串前添加空格,使索引从1开始
    s1 = " " + s1;
    s2 = " " + s2;
  
    // 初始化DP数组:空字符串有1种匹配方式
    for (int i = 0; i <= 2; i++)
    {
        dp[i][0][0][0] = 1;
    }
  
    // 动态规划填表(使用滚动数组优化空间)
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            for (int k = 1; k <= x; k++)
            {
                // 不选择s1[i]作为匹配结尾的情况
                dp[1][j][k][0] = (dp[0][j][k][0] + dp[0][j][k][1]) % MOD;
              
                // 选择s1[i]作为匹配结尾的情况
                if (s1[i] == s2[j])
                {
                    dp[1][j][k][1] = (dp[0][j-1][k][1] + 
                                     dp[0][j-1][k-1][1] + 
                                     dp[0][j-1][k-1][0]) % MOD;
                }
                else
                {
                    dp[1][j][k][1] = 0;  // 字符不匹配,方案数为0
                }
            }
        }
        // 滚动数组:将当前状态复制到前一状态
        memcpy(dp[0], dp[1], sizeof(dp[0]));
    }
  
    // 输出结果:总方案数取模
    cout << (dp[1][m][x][0] + dp[1][m][x][1]) % MOD;
  
    return 0;
}

【运行结果】

6 3 1 
aabaab 
aab
2
posted @ 2026-02-20 19:20  团爸讲算法  阅读(1)  评论(0)    收藏  举报