zoukankan      html  css  js  c++  java
  • NOIP2015Day2T2 子串

    原题链接
    题解原创

    题目描述

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

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

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

    输入格式

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

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

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

    输出格式

    输出共一行,包含一个整数,表示所求方案数。

    由于答案可能很大,所以这里要求输出答案对 1,000,000,007 取模的结果。

    数据范围

    [1 le n le 1000, \\ 1 le m le 200,\\ 1 le k le m\\ ]

    输入样例:

    6 3 1 
    aabaab 
    aab
    

    输出样例:

    2
    

    解题报告

    题意理解

    就是让你从字符串A中,拿出(k)个子串,子串之间不得有重复,要求(k)个子串,按照拿出来的顺序,可以拼接成为字符串B.

    要你求出符合条件的方案总数.

    所有合法方案如下:(加下划线的部分表示取出的子串)

    样例一:aab aab / aab aab

    样例二:a ab aab / a aba ab / a a ba ab / aab a ab / aa b aab / aa baa b / aab aa b

    样例三:a a b aab / a a baa b / a ab a a b / a aba a b / a a b a a b / a a ba a b / aab a a b


    思路解析

    算法确定

    一般来说,当我们看到由于答案可能很大,所以这里要求输出答案对 1,000,000,007 取模的结果。这句话的时候,一般来说动态规划算法是可以基本确定的了.

    每一个字符串,其实我们都可以看作是字符区间.

    我们接着来分析题目的内容.

    我们发现,对于题目而言,一个非常重要的点就是,他是一个区间.

    区间有关的动态规划,最基本的有哪些呢?

    1. 区间DP
    2. 线性DP
    3. 数位DP

    我们发现线性DP似乎是一个不错的选择,因为区间DP,这道题目似乎并木有给出明显的区间划分,反而都是线性的思想比较明显.

    至于数位DP跟本题没有任何关系.

    所以现在我们完全可以确定,本题的算法就是线性动态规划


    状态设置

    我们观察这个题目,题目的大致意思,就是让我们选取一些字符串出来,去构造字符串B.

    那么我们状态中,必须有以下这些信息.

    1. 关于字符串A的信息.
    2. 关于字符串B的信息
    3. 我们当前用了多少个字符串,因为我们最多只能选取k个字符串.

    这就是我们可以确定的必备信息条件,那么接下来我们如何设置状态表示呢?

    根据上面的信息表,我们不难想到我们的基本状态如何设置.

    [f[i][j][k]表示为字符串A的前i个字符,我们已经选出了j个字符匹配字符串B,然后我们一共取出了K个字符串. ]

    举个例子,让我们更好的理解一下.

    [比如说f[5][4][2]表示为 \ 我们选取了5个字符在串A中,然后其中有四个字符匹配字符串B,我们一共取出了2个字符串. \\ 串A:ABCBD \\ 串B:ABBD \\ 那么显然我们从字符串A中选取出来的是AB,BD,中间的C被我们抛弃了. \\ ]

    但是我们发现我们的状态转移方程不太好想出来,为什么呢?

    我们发现对于K的转移,我们很难划分.

    比如说我们当前选择了第(j)个字符.

    但是我们不知道我们之前的(j-1)这个字符是否选择了.

    如果选择了,那么我们的k不需要变化.

    没有选择,我们的k就需要+1.

    综上所述,我们不得不处理以下这个选取问题.

    我们可以增加一个维度,去存储当前字符是否选择了.

    [f[i][j][k][0/1]表示为串A前i个字符,选取了j个字符,一共选取了k个字符串 \\ 0表示第i个字符没有选择,1表示第i个字符选择了1 \\ ]


    转移方程

    1.当前(a[i]==b[j])

    这是什么意思?就是我们当前这个字符,可以匹配B串了.

    f[i][j][k][1]=(f[i][j-1][k][1]+f[i][j-1][k-1][0])%Mod;
    f[i][j][k][0]=(f[i][j][k][0]+f[i][j][k][1])%Mod;
    

    我们来慢慢分析每一句话.

    f[i][j][k][1]=(f[i][j-1][k][1]+f[i][j-1][k-1][0])%Mod;
    

    对于这句话而言.

    假如说,我们当前选择(i)个字符.

    [f[i][j-1][k][1]表示为 \\ 前i-1个字符串中,选择了j-1个字符,同时i-1个字符我们也选择了 \\ 因为我们这个字符和前面的字符相连接了,所以我们不需要更多的代价 \\ f[i][j-1][k-1][0]表示为 \\ 前i-1个字符串中,选择了j-1个字符,但是i-1个字符我们没有选择 \\ 所以只能使用k-1个字符串,因为我们当前字符得花费一个字符串代价. \\ ]

    接着我们分析第二句话.

    f[i][j][k][0]=(f[i-1][j][k][0]+f[i-1][j][k][1])%Mod;
    

    假如说我们当前不选择(i)个字符.

    就是这一位取的+这一位没取的(也就是上一段的0)

    Hint:空间太大,所以我们要状态压缩.

    我们发现第一维(i)其实并没有什么作用.

    所以我们使用背包问题的优化思想,让循环反过来,然后这道题目就解决了.


    #include <bits/stdc++.h>
    using namespace std;
    const int Mod=1000000007;
    #define read() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) //读入优化
    string a,b;
    int n,m,k2,f[210][210][2];
    int main()
    {
    	read();
    	cin>>n>>m>>k2>>a>>b;
    	a=" "+a;//字符串有用部分从1开始
    	b=" "+b;//字符串有用部分从1开始
    	f[0][0][0]=1;
    	for(int i=1; i<=n; i++)
    		for(int j=m; j; j--)//循环反过来
    			if (a[i]==b[j])//相等判定
    			{
    				for(int k=min(j,k2); k; k--)//k必然小于j,而且不能超过范围
    				{
    					f[j][k][1]=(f[j-1][k][1]+f[j-1][k-1][0])%Mod;
    					f[j][k][0]=(f[j][k][0]+f[j][k][1])%Mod;
    				}
    			}
    			else
    				for(int k=1; k<=m; k++)//初始化把
    					f[j][k][1]=0;
    	cout<<(f[m][k2][0])%Mod;
    	return 0;
    }
    
  • 相关阅读:
    RabbitMQ知识点整理2-相关概念介绍
    RabbitMQ知识点整理1-生产和消费消息
    Java自学-图形界面 Swing中的线程
    Java自学-图形界面 日期控件
    Java自学-图形界面 表格
    Java自学-图形界面 工具栏
    WebStorm 2020.2.3 破解(Mac,windows,Linux)
    消息队列的使用场景
    有关建立虚拟环境的总结
    性能测试-Locust分布式执行
  • 原文地址:https://www.cnblogs.com/gzh-red/p/11172285.html
Copyright © 2011-2022 走看看