• Common Subsequence(线性dp,二维前缀和)


    题意

    给出两个长度分别为\(N\)\(M\)的整数序列\(S\)\(T\),它们均由\(1\)\(10^5\)之间的整数组成。求在\(S\)子序列和\(T\)子序列中,有多少对两个子序列的内容相同。

    注意:
    \(A\)的子序列是指通过从\(A\)删除零个或多个元素而不改变顺序而获得的序列。

    对于\(A\)的两个子序列,如果内容相同,但是被删除元素的位置不同,也当成两个不同的子序列。

    数据范围

    \(2 \leq N, M \leq 2 \times 10^3\)

    思路

    • 方法1

    首先介绍官方题解的方法,这个方法容易理解,但是不太容易想到。

    \(f(i, j)\)表示使用\(S\)的前\(i\)个元素和\(T\)的前\(j\)个元素,并且\(S\)的第\(i\)个元素必选,\(T\)的第\(j\)个元素必选的内容相同子序列对的个数。

    如果\(S_i \neq T_j\),那么\(f(i, j) = 0\)。如果\(S_i = T_j\),则通过枚举两个序列的结尾进行转移,即:\(f(i, j) = \sum\limits_{k = 1}^{i - 1} \sum\limits_{l = 1}^{j - 1} f(k, l) + 1\)。其中,\(f(0, 0) = 1\)

    但是运行时间是\(O(N^2 \times M^2)\),显然超时。因此我们考虑使用前缀和优化。

    \(s(i, j) = \sum\limits_{k = 1}^{i} \sum\limits_{l = 1}^{j} f(k, l)\),即\(s(i, j)\)\(f\)的前缀和。

    \(s(i, j) = s(i - 1, j) + s(i, j - 1) - s(i - 1, j - 1) + f(i, j)\)

    利用\(s(i, j)\),我们可以将转移方程改写成\(f(i, j) = s(i - 1, j - 1) + 1\)

    最终答案为\(s(n, m) + 1\)

    • 方法2

    下面介绍一种较为自然的状态表示方法,但是转移方程略难考虑。

    由最长公共子序列产生灵感,令\(f(i, j)\)表示使用\(S\)的前\(i\)个元素和\(T\)的前\(j\)个元素的内容相同子序列对的个数。

    \(f(i, j)\)可以由\(f(i - 1, j)\)\(f(i, j - 1)\)\(f(i - 1, j - 1)\)转移而来,那么具体转移而来呢?

    根据容斥原理,\(f(i, j)\)可以为包含\(S_i\)\(S_i\)可选)的所有情况加上包含\(T_j\)的所有情况减去\(S_i\)\(T_j\)都不包含加上\(S_i\)\(T_j\)必选的情况。这个思考方式与二维前缀和的思考方式类似,可以类比进行思考。

    转移方程可以写为。若\(S_i = T_j\),则\(f(i, j) = f(i - 1, j) + f(i, j - 1) - f(i - 1, j - 1) + f(i - 1, j - 1) = f(i - 1, j) + f(i, j - 1)\);若\(S_i \neq T_j\),则\(f(i, j) = f(i - 1, j) + f(i, j - 1) - f(i - 1, j - 1)\)

    代码

    //方法1
    
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    const int N = 2010, mod = 1e9 + 7;
    
    int n, m, a[N], b[N];
    ll f[N][N], sum[N][N];
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
        for(int i = 1; i <= m; i ++) scanf("%d", &b[i]);
        f[0][0] = 1;
        for(int i = 1; i <= n; i ++) {
            for(int j = 1; j <= m; j ++) {
                if(a[i] == b[j]) f[i][j] = (sum[i - 1][j - 1] + 1) % mod;
                sum[i][j] = (sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + f[i][j] + mod) % mod;
            }
        }
        printf("%lld\n", 1 + sum[n][m]);
        return 0;
    }
    
    //方法2
    
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    const int N = 2010, mod = 1e9 + 7;
    
    int n, m, a[N], b[N];
    ll f[N][N];
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
        for(int i = 1; i <= m; i ++) scanf("%d", &b[i]);
        for(int i = 0; i <= n; i ++) f[i][0] = 1;
        for(int j = 0; j <= m; j ++) f[0][j] = 1;
        for(int i = 1; i <= n; i ++) {
            for(int j = 1; j <= m; j ++) {
                if(a[i] != b[j])
                    f[i][j] = (f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + mod) % mod;
                else
                    f[i][j] = (f[i - 1][j] + f[i][j - 1]) % mod;
            }
        }
        printf("%lld\n", f[n][m]);
        return 0;
    }
    
  • 相关阅读:
    P1121 环状最大两段子段和
    无题
    cdoj 1485 柱爷搞子串 sam treap
    自然数幂和
    Gym 100341C AVL Trees NTT
    线性筛分解质因子
    codeforces 366 Ant Man dp
    UVALive 6914 Maze Mayhem 轮廓线dp
    hdu 5790 Prefix 字典树 主席树
    莫比乌斯反演个人小结
  • 原文地址:https://www.cnblogs.com/miraclepbc/p/16339733.html
Copyright © 2020-2023  润新知