• 【NOIP2015提高组】子串 区间DP+滚动数组优化


    题意:

    有两个仅包含小写英文字母的字符串 A 和 B。

    现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一个新的字符串。

    请问有多少种方案可以使得这个新串与字符串 B 相等?

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

    数据范围:

    对于第 1 组数据:1≤n≤500,1≤m≤50,k=1;
    对于第 2 组至第 3 组数据:1≤n≤500,1≤m≤50,k=2
    对于第 4 组至第 5 组数据:1≤n≤500,1≤m≤50,k=m
    对于第 1 组至第 7 组数据:1≤n≤500,1≤m≤50,1≤k≤m
    对于第 1 组至第 9 组数据:1≤n≤1000,1≤m≤100,1≤k≤m
    对于所有 10 组数据:1≤n≤1000,1≤m≤200,1≤k≤m

    ------------------------------------------------------我是分割线----------------------------------------------------

    题解:一道状态不太明显的题,我们首先要寻找这道题的切入点。

    观察这道题的特点,我们发现,相对于B,A中的每个字符显然有选与不选两种决策。

    而对于这两个字符串,可以用两个指针表示,一个指向A的状态,一个指向B的状态。

    于是这道题的状态就很被发现了。

    设置状态:

    设 F(i,j,k,1) 表示A的前i个字符中,匹配到B中当前第j个字符,一共使用了k段,选择当前第i个字符的方案数。

    F(i,j,k,0)  表示A的前i个字符中,匹配到B中当前第j个字符,一共使用了k段,不选择当前第i个字符的方案数。

    然后就是状态转移:

    由状态,我们可以推出如下状态转移方程:

    (1) 当Ai == Bj 时,F(i,j,k,0) = F(i-1,j,k,0)+ F(i-1,j,k,1).

         即当前A中第i个数不作为答案的方案数 == 前i-1的方案数总和。

               F(i,j,k,1) = F(i-1,j-1,k-1,0) + F(i-1,j-1,k-1,1) + F(i-1,j-1,k,1);

         即当前A中第i个数作为答案的方案数 == A中前i-1个数匹配到B中第j-1个的方案数总和(i不接到前一个答案中) + A中前i-1个数方案(i接到前一个答案中)。  

    (2)当Ai  != Bj 时, F(i,j,k,0) = F(i-1,j-1,k,0) + F(i-1,j-1,k,1).

                 同(1)中的转移。

               F(i,j,k,1) = 0;  

              由于当前数不能作为答案,所以方案为0.

    再确定边界条件,我们可以知道,F(i,0,0,0) = 1;即匹配B中0个字符的方案为1.

    于是我们可以写出代码:

    #include<bits/stdc++.h>
    
    #define ll long long
    #define mp make_pair
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    
    using namespace std;
    
    typedef pair<int, int> pii;
    typedef double db;
    const int mod = 1e9+7;
    const int N = 1e6 + 50;
    int n, m, p;
    int f[1010][101][101][2];
    char a[N], b[N];
    inline int read() {
        int x = 0, f = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}
        while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}
        return x*f;
    }
    void work(){
        rep(i, 0, n) f[i][0][0][0] = 1;
        rep(i, 1, n) rep(j, 1, m) rep(k, 1, p){
            if(a[i] == b[j]) {
                f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1])%mod;
                f[i][j][k][1] = (f[i-1][j-1][k-1][0] + (f[i-1][j-1][k-1][1] + f[i-1][j-1][k][1]) % mod) % mod;
            }
            else {
                f[i][j][k][1] = 0;
                f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1])%mod;
            }
        }
        printf("%d
    ", (f[n][m][p][1] + f[n][m][p][0])%mod); 
    }
    void init(){
        n = read(); m = read(); p = read();
        scanf("%s%s", a+1, b+1);
    }
    int main(){
        init(); 
        work();
        return 0;
    }
    View Code

    但是我们发现,这份代码空间复杂度效率低下(2*n*m*k),无法通过此题,我们还需要优化。

    于是乎,DP常用的空间优化:滚动数组优化就出现了。

    观察DP转移方程,我们可以发现,每一个决策i只与前一个决策i-1有关,其他的空间都是多余的。

    所以我们就可以用01方法表示。

    AC代码如下:

    #include<bits/stdc++.h>
    
    #define ll long long
    #define mp make_pair
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    
    using namespace std;
    
    typedef pair<int, int> pii;
    typedef double db;
    const int mod = 1e9 + 7;
    const int N = 1e6 + 50;
    int n, m, p;
    int f[2][220][220][2];
    char a[N], b[N];
    inline int read() {
        int x = 0, f = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}
        while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}
        return x*f;
    }
    void work(){
        int val = 1;
        f[0][0][0][0] = f[1][0][0][0] = 1; 
        rep(i, 1, n) {
            rep(j, 1, m) rep(k, 1, p){
                if(a[i] == b[j]) {
                    f[val][j][k][0] = (f[val^1][j][k][0] + f[val^1][j][k][1])%mod;
                    f[val][j][k][1] = (f[val^1][j-1][k-1][0] + (f[val^1][j-1][k-1][1] + f[val^1][j-1][k][1]) % mod) % mod;
                }
                else {
                    f[val][j][k][1] = 0;
                    f[val][j][k][0] = (f[val^1][j][k][0] + f[val^1][j][k][1])%mod;
                }
            }
            val ^= 1;
        }
        printf("%d
    ", (f[n&1][m][p][1] + f[n&1][m][p][0])%mod); 
    }
    void init(){
        n = read(); m = read(); p = read();
        scanf("%s%s", a+1, b+1);
    }
    int main(){
        init(); 
        work();
        return 0;
    }
    View Code

    总结:这道题作为线性DP的练习题(NOIP的题),有一定的思维难度,对DP思维提升有很大的帮助。

  • 相关阅读:
    php 本地 备份远程mysql和mdb 多任务只执行一次
    c# 监控服务器上传木马(包含可疑文件)
    jquery 简短 右键菜单 兼容ie6 ie7 ie8 firefox chrome
    分解从身份证中读回的户籍地址
    备份mysql(一表一文件)
    THINKPHP 3.0 整合KINDEDITOR 4.05
    c# 仿照计划任务(定时提示、定时运行程序、定时打开url(前台/后台))/每天/每周/每月/一次 多时间段
    php基本操作echo
    c# 扫描可疑文件(找到木马)(简)
    GOOGLE 地图,查询地名,移动标记,生成静态地图
  • 原文地址:https://www.cnblogs.com/smilke/p/11587313.html
Copyright © 2020-2023  润新知