经典DP——LCS,LIS……

最长递增子序列(LIS)

O(n2 O ( n 2 )算法

设a[i]为给定序列的第i个元素的值,dp[i]表示以第i个元素结尾的最长递增子序列长度
– 状态转移方程:

dp[i] = max{dp[i], dp[j] + 1} (a[i] > a[j] && i > j)
  • O(n2 O ( n 2 )代码模板
for (i=1; i<=n; ++i)
    dp[i] = 1;
for (i=1; i<=n; ++i)
    for (j=1; j<i; ++j)
    if (a[i] > a[j])
        dp[i] = max(dp[i], dp[j]+1);

O(nlogn) O ( n l o g n ) 算法

a[i]为给定序列的第i个元素的值,dp[i]存放以i为结尾最长递增子序列的长度,B[i]表示最长序列长度为len时可以构成最长长度的第i位的最小值

B[1] = a[1];   
len = 1;                    //存最长序列长度
dp[1] = len;
for(int i=2; i<=n; i++)
{
    if (a[i] > B[len])       //直接插入序列最后
    {
        len++;
        dp[i] = len;
        B[len] = a[i];
    }
    else
    {
        int p = lower_bound(B+1, B+len+1, a[i]) - B;
        //为了使B序列潜力最大,所以用小的去替换大的序列元素
        dp[i] = p;
        B[p] = a[i];
    }
}

最长递增子序列(LCS)

O(n2) O ( n 2 ) 算法

定义:给定两个序列,求他们相同的子序列中最长的长度。
x[1-n], y[1-m], dp[i][j]表示x从1到i,y从1到j的相同子序列最长长度

memset(dp, 0, sizeof(dp));

for (i=1; i<=n; ++i)
    for (j=1; j<=m; ++j)
    { if (x[i] == y[j]) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = max(dp[i-1][j], dp[i][j-1]); }

O(nlogn) O ( n l o g n ) 算法

最长公共子序列 的 nlogn 的算法本质是 将该问题转化成 最长增序列(LIS),因为 LIS 可以用nlogn实现,所以求LCS的时间复杂度降低为 nlogn。

证明
参考
转化:将LCS问题转化成LIS问题。
假设有两个序列 s1[ 1~6 ] = { a, b, c , a, d, c }, s2[ 1~7 ] = { c, a, b, e, d, a, b }。记录 s1 中每个元素在 s2 中出现的位置, 再将位置按降序排列, 则上面的例子可表示为:loc( a)= { 6, 2 }, loc( b ) = { 7, 3 }, loc( c ) = { 1 }, loc( d ) = { 5 }。将s1中每个元素的位置按s1中元素的顺序排列成一个序列s3 = { 6, 2, 7, 3, 1, 6, 2, 5, 1 }。在对s3求LIS得到的值即为求LCS的答案

最长公共子串(子串必须连续)

  • 代码
memset(dp, 0, sizeof(dp));

for (i=1; i<=n; ++i)
    for (j=1; j<=m; ++j)
    { if (x[i] == y[j]) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = 0; }

最大连续子序列和

给定一个整数序列A0,A1,…,An-1。求最大连续子序列的和。为了方便,若所有数都为负数,输出0。(不是很懂这里什么意思,按理说应该是遍历一遍取最大的那个负数就可以了)

a[1-n]表示数组,dp[i]表示以i为结尾的最大连续子序列和

dp[0] = 0;

for (int i=1; i<=n; ++i)
    if (dp[i-1] > 0)
        dp[i] = dp[i-1] + a[i];
    else
        dp[i] = a[i];

例题

hdu 5586 sum

  • 题目大意:给出一个数字串 A1 ~ An,你可以选择一个连续的子串 [l, r],使得里面所有的数字 Ai 的值变成 f(Ai) = (1890 * Ai + 143) % 10007,或者你也可以不要选择。最后要求所有数字的和最大是多少。
  • 分析:最大连续子段和。要使得最后的数字和最大,那么就是求一个区间 [l, r] 使得增量最大,也就是说使得 Σ (f(Ai) – Ai) 最大,那么很明显就转换成了最大连续子段和问题了。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn=100005;
const int mod=10007;
long long int a[maxn],f[maxn],dp[maxn];

int main()
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        long long ans=0;
        for(int i=1; i<=n; i++)
            {
                scanf("%d", &a[i]);
                f[i] = (1890 * a[i] + 143) % mod - a[i];
                ans += a[i];
            }

        dp[0] = 0;

        int res = 0;
        for (int i=1; i<=n; i++)
        {
            if (dp[i-1] > 0)
                dp[i] = dp[i-1] + f[i];
            else
                dp[i] = f[i];

            if (dp[i] > res)
                res = dp[i];
        }

        printf("%lld\n", ans+res);
    }
    return 0;
}

基础练习:

HDU 1003 最大连续子序列和

HDU 1159 最长公共子序列

HDU 1513 回文串(LCS)

HDU 5748 最长递增子序列(LIS)

参考文献:酷酷学姐的PPT

点赞