noip2015 子串(心得体会)

今天终于又在联赛提高组中遇到动态规划(Dynamic program)了,~~(><) ~~ ,在各种算法中,稍微掌握得好一点的一块。O(∩∩)O~。侥幸在四人组中拿到头魁,(李兄的强项在AVL树, 红黑树 ,线段树,伸展树, 跳跃表,单调队列,最近公共祖先,倍增,后缀自动机,ac自动机等等高端的应用上,简单动态规划有些薄弱,【但是树形dp,状压dp又超级强,看不懂他】今天勉强打败他,在此分享李兄的博客地址http://blog.csdn.net/lycheng1215)
拿到动态规划,第一步,分解问题。注意单纯的递归往往会导致子问题被重复计算,用动态规划的方法,子问题的解一旦求出就要被保存,所以每个子问题只需求解一次。子问题经常和原问题形式相似,有时甚至完全一样,只不过规模从原来的n 变成了n-1,或从原来的n×m 变成了n×(m-1) ……等等。这道题就是依次找到在子串A的n-1位置,有多少种方案可以使得这个找出的新串与字符串 B 相等。对B的话,也有子问题,那就是到B得j-1位置的时候,在i中能提取多少出来。
找到子问题,就意味着找到了将整个问题逐渐分解的办法。 分解下去,直到最底层规模最小的的子问题可以一目了然地看出解。最小子问题就是到1位置,有多少种方案,显然长度小于B串的话,这里是0。
用动态规划解题时,将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题,所谓某个“状态”下的“值”,就是这个“状态”所对应的子问题的解。对于这道题的状态就是在A串的i位置及前面所有位置中,取k次,能有多少个和B串的前j部分相同的新串。称为s[i][j][k]。
定义出什么是“状态”,以及在该 “状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。那么怎么用状态转移方程把这个n上的问题转移到1上呢?这个就是核心了。
想一想,s[i][j][k]可以分解成什么?
是不是如果A串的i位和B串j位对应上了,s[i][j][k]=1.不选i位置的字符的提取相同量+2.选了i位置的字符的提取相同量。1.是不是就是s[i-1][j][k]?那么2.怎么办呢?我选择的是开一个辅助的f[i][j][k],用于表示2.
问题又来了,如何给维护f[i][j][k]呢?
思考,再思考,继续分解,分析
现在i位置和j位置的相等这个条件满足了,那是不是i-1位置和j-1提出来的相等子串有好多个,这里就有好多个?就是相当于之前提出来的子串,假设是s,而现在提出来的子串是s+A[i],个数是不是相等的?
答案是:不是。
大神是这样说的:如果 A[i-1] = B[j-1]A[i−1]=B[j−1],那么串 AA 的 i - 1i−1 位置就可以跟 i - 2i−2 位置合成一组(如果可能的话),这时组数不变;也可以不跟 i - 2i−2 位置合成一组,自成一组,这时组数+1。
oh,no。

#include 
#include 
#include 
using namespace std;
const int maxn = 505, maxm = 55, mod = 1000000007;
int m, n, k;
char s1[maxn], s2[maxm];
int f[maxn][maxm][maxm], s[maxn][maxm][maxm];
void dp() {
    s[0][0][0] = 1;
    for(int i = 1; i <= n; ++i) {
        s[i][0][0] = 1; 
        for(int j = 1; j <= m; ++j)
            if(s1[i-1] == s2[j-1]) {
                for(int t = 1; t <= min(k, j); ++t) {
                    f[i][j][t]=(s[i-1][j-1][t-1] + f[i-1][j-1][t]) % mod,
                    s[i][j][t]=(s[i-1][j][t] + f[i][j][t]) % mod;
                }
            }else for (int t=1;t<=min(k, j); ++t) s[i][j][t] = s[i-1][j][t]; 
    }
    printf("%d\n", s[n][m][k]);
}
int main() {
    freopen("substring.in","r",stdin);
    freopen("substring.out","w",stdout);
    scanf("%d%d%d%s%s", &n, &m, &k, s1, s2);
    dp();
    return 0;
}

然后,只能过7个点啦,范围不够。
这里要学学新的算法。
滚动数组。
滚动数组的作用在于优化空间,主要应用在递推或动态规划中(如01背包问题)。因为DP题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,前面的解往往可以舍去。所以用滚动数组优化是很有效的。利用滚动数组的话在N很大的情况下可以达到压缩存储的作用。
原理很简单,李兄说浅显易懂,连我都只用了20minute就理解了,大家自己搜一搜

你可能感兴趣的:(noip2015 子串(心得体会))