单调递增最长子序列(LIS)&&最长公共子序列(LCS)

  动态规划的经典问题,这里的讲解网上有很多非常好的,而我却还没有能力写出完整详细的理解,就附图两张吧,这两个图貌似很火,出处找不到了。

LIS

LCS

  动态规划的三种形式:

1.记忆递归型

2.“我为人人”递推型

3.“人人为我”递推型

  递推并不是动态规划的本质,如何拆分才是核心。拆分成若干子问题.

这里看到知乎上讲解特别好的

动态规划中递推式的求解方法不是动态规划的本质

动态规划的本质不在于是递推或是递归,也不需要纠结是不是内存换时间

实际问题的解决

百练2757-最长上升子序列

这里附三种解法:

递归

/*
вснйлБё╨
в╢л╛
*/
#include<iostream>
#include<cstdio>
using namespace std;
const int INF = 99999999;
const int N = 105;
int a[N];//,dp[N][N];
int Sum(int m,int n)
{
    if(m == 0)
        return 1;
    if(n <= 0)
        return 0;
    return Sum(m,n-1) + Sum(m-a[n],n-1);
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i = 1;i <= n;i++)
            scanf("%d",&a[i]);
            cout<< Sum(40,n) <<endl;
    }
    return 0;
}

“我为人人”递推型

#include<iostream>//“我为人人”递推型动归
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1005;
int a[N],maxnum[N];
int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&a[i]);
        maxnum[i] = 1;//初始化,最初状态
    }
    for(int i = 2;i <= n;i++)//每次以第i个数为终点的序列的最长长度
    {
        for(int j = i+1;j <= n;j++)//查看i为终点中的j为终点上升序列
            if(a[j] > a[i])//进行增长
                maxnum[j] = max(maxnum[j],maxnum[i] +1);
    }
    cout<< *max_element(maxnum+1,maxnum+n+1) <<endl;//这个函数很奇妙
}

3.“人人为我”递推型

#include<iostream>//”人人为我“型递推
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1005;
int a[N],maxnum[N];
int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&a[i]);
        maxnum[i] = 1;//初始化,最初状态
    }
    for(int i = 2;i <= n;i++)//每次以第i个数为终点的序列的最长长度
    {
        for(int j = 1;j < i;j++)//查看i为终点中的j为终点上升序列
            if(a[i] > a[j])//进行增长
                maxnum[i] = max(maxnum[i],maxnum[j] +1);
    }
    cout<< *max_element(maxnum+1,maxnum+n+1) <<endl;//这个函数很奇妙
}

NYOJ-17 单调递增最长子序列 LIS

/*
最长上升子序列
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char str[10005];
int dp[10005];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        scanf("%s",str);
        memset(dp,0,sizeof(dp));
        int len = strlen(str);
        //LIS
        for(int i = 0;i <len;i++)
        {
            dp[i] = 1;
            for(int j = 0; j < i;j++)
                if(str[i] > str[j] && dp[i] < dp[j] + 1)
                    dp[i] = dp[j] + 1;
        }
        int maxn = -1;
        for(int i = 0;i < len;i++)
            maxn = max(maxn,dp[i]);
        printf("%d\n",maxn);
    }
    return 0;
}


这里LIS还有经典的拦截炮弹问题

NYOJ 79拦截炮弹

/*
拦截导弹
这类问题还真不少,让求非增最长子序列,只需要按照
单调递增子序列的算法修改为a[i] < a[j]就行了
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int a[30],dp[30];
int main()
{
    int t,n;
    cin >> t;
    while(t--)
    {
        cin >> n;
        for(int i = 1;i <= n;i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        //LIS,这个是单调非增
        for(int i = 1;i <= n;i++)
        {
            dp[i] = 1;//最小为1
            for(int j = 1;j < i;j++)//以后每一发炮弹都不能高于前一发的高度
                if(a[i] < a[j] && dp[i] < dp[j] + 1)
                    dp[i] = dp[j] + 1;
        }
        int maxn = 0;
        for(int i = 1;i <= n;i++)
            maxn = max(maxn,dp[i]);
        printf("%d\n",maxn);
    }
    return 0;
}
//nnd,提交了两次样例,rita,气煞我了

拦截炮弹2

NYOJ36最长公共子序列  LCS

//根据下标不同,有两种写法
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char s1[1005],s2[1005];
int dp[1005][1005];
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        scanf("%s%s",s1,s2);
        memset(dp,0,sizeof(dp));
        int len1 = strlen(s1);
        int len2 = strlen(s2);
        for(int i = 0;i < len1;i++)//前i个,前j个
        {
            for(int j = 0;j < len2;j++)
            {
                if(s1[i] == s2[j])
                    dp[i+1][j+1] = dp[i][j] + 1;
                else
                    dp[i+1][j+1] = max(dp[i+1][j],dp[i][j+1]);
            }
        }
        printf("%d\n",dp[len1][len2]);//前len1个字符和前len2个字符
    }
    return 0;
}


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char s1[1005],s2[1005];
int dp[1005][1005];
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        scanf("%s%s",s1+1,s2+1);
        memset(dp,0,sizeof(dp));
        int len1 = strlen(s1+1);
        int len2 = strlen(s2+1);
        for(int i = 1;i <= len1;i++)//前i个,前j个
        {
            for(int j = 1;j <= len2;j++)
            {
                if(s1[i] == s2[j])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
            }
        }
        printf("%d\n",dp[len1][len2]);//前len1个字符和前len2个字符
    }
    return 0;
}

NYOJ 214 单调递增子序列(二)

因为数据量非常大,n^2铁定超时,所以要用二分的dp,讲解见于Here,我是看的这篇讲解

 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 100005;
int a[N],dp[N];
int Binarysearch(int k,int right)
{
    int left = 1,mid = (right+left)/2;
    while(left <= right)
    {
        if(k > dp[mid])
            left = mid + 1;
        else if(k < dp[mid])
            right = mid - 1;
        else
            return mid;
        mid = (right+left)/2;
    }
    return left;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i = 1;i <= n;i++)
            scanf("%d",&a[i]);

        int len = 1,pos;
        dp[1] = a[1];
        for(int i = 2;i <= n;i++)
        {
            pos = Binarysearch(a[i],len);
            dp[pos] = a[i];
            if(pos > len)   len = pos;
        }
        printf("%d\n",len);
    }
    return 0;
}
        

但是按照其原理这样实现更好理解(差不多)

for(int i = 2;i <= n;i++)
        {
            if(a[i] > dp[len])
                dp[++len] = a[i];
            else
            {
                pos = Binarysearch(a[i],len);
                dp[pos] = a[i];
            }
        }


为啥lower_bound不可以呢,求大牛告知

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值