dp算法

一、动态规划

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决

明确状态和转移两个问题

二、例题

例一:数字三角形

有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数.
1
3 2
4 10 1
4 3 2 20
从第一行的数开始,每次可以往左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来,如何走才能使得这个和尽量大?
输入:三角形的行数n,数字三角形的各个数(从上到下,从左到右)
输出:最大的和。
input 4
1
3 2
4 10 1
4 3 2 20
output 24

状态

f[i][j]表示从(1,1)出发走到(i,j)所有路径的最大和。

转移:

考虑哪些状态对f[i][j]这个状态有影响。
(i-1,j)->(i,j)
(i-1,j-1)->(i,j)
如何转移?
f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j]
把(1,1)到(i,j)的路径分成两类,(1,1)->(i-1,j)->(i,j) 和 (1,1)->(i-1,j-1)->(i,j)

代码:

#include<iostream>
#include<algorithm>
#define maxn 110
#define INF -100000000
using namespace std;
int a[maxn][maxn];
int n;
int main()
{
    while(cin>>n&&n)
    {
        for(int i=0;i<=n+1;i++)
            for(int j=0;j<=n+1;j++)
            a[i][j]=INF;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=i;j++)
                cin>>a[i][j];
        for(int i=2;i<=n;i++)
            for(int j=1;j<=i;j++)
                a[i][j]=a[i][j]+max(a[i-1][j],a[i-1][j-1]);
        int maxnn=a[n][1];
        for(int i=2;i<=n;i++)
            maxnn=max(maxnn,a[n][i]);
        cout<<maxnn<<endl;
    }
    return 0;
}

例二:题目描述
给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
示例
input:
7
3 1 2 1 8 5 6
output:
4
答案是:1 2 5 6)
状态:
f[i]表示以i结尾的上升子序列中的最长长度。
转移

f[i]=max(f[j])+1
j<j且a[i]<a[j]
代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 210
using namespace std;
int a[maxn],b[maxn];
int n;
int main()
{
    while(cin>>n)
    {
        for(int i=1; i<=n; i++)
            cin>>a[i];
        for(int i=1; i<=n; i++)
            b[i]=1;
        for(int i=1; i<=n; i++)
        {
            int maxnn;
            maxnn=0;
            for(int j=1; j<i; j++)
            {
                if(a[j]<a[i])
                {
                    maxnn=max(b[j],maxnn);
                }
            }
            b[i]=maxnn+1;
        }
        sort(b+1,b+n+1);
        for(int i=1;i<=n;i++)
        cout<<b[i]<<endl;
    }
    return 0;
}

优化:O(n*logn)(二分+贪心)//记录最后一个元素的最小值,不断二分求出最完美的答案
代码

#include<iostream>
#include<algorithm>
#define maxn 120
using namespace std;
int a[maxn];
int n;
int minn[maxn];//存储数据最后一个元素的最小值 
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	    cin>>a[i];
	for(int i=0;i<n;i++)
	     minn[i]=-1;
	minn[0]=a[0];
	int xu=0;
	for(int i=1;i<n;i++)
	{
		if(a[i]>minn[xu])
		{
			xu++;
			minn[xu]=a[i];
	    }
		else
		{
			int k=lower_bound(minn,minn+xu,a[i])-minn;
			minn[k]=a[i];
		}
	}
	cout<<xu+1;
	return 0;
}

例三、最长公共子序列

如果Z既是X的子序列,又是Y的子序列,则称Z为X和Y的公共子序列。

状态:a[i][j]=表示截止到第i行第j列最大公共子序列的长度

转移:如果s1[i]!=s2[j],那么a[i][j]=max(a[i][j-1],a[i-1][j]),反之,a[i][j]=a[i-1][j-1]+1

代码

#include<iostream>
#include<cstring>
#include<algorithm>
#define maxn 110
using namespace std;
int N,M;
int a[maxn][maxn];
string s1,s2;
int main()
{
    while(cin>>N>>M&&M)
    {
        cin>>s1>>s2;
        memset(a,0,sizeof(a));
        for(int i=1;i<=N;i++)
        {
            for(int j=1;j<=M;j++)
            {
                if(s1[i]!=s2[j])
                    a[i][j]=max(a[i-1][j],a[i][j-1]);
                else
                    a[i][j]=a[i-1][j-1]+1;
            }
        }
        int maxx=0;
        for(int i=1;i<=N;i++)
            for(int j=1;j<=M;j++)
                maxx=max(maxx,a[i][j]);
        cout<<maxx<<endl;
    }
    return 0;
}

例题、最大字段和

问题描述
求一个序列的最大子段和即最大连续子序列之和。例如序列[4, -3, 5, -2, -1, 2, 6, -2]的最大子段和为11=[4+(-3)+5+(-2)+(-1)+(2)+(6)]。
问题解析:

因为要用bp来做,所以我们先将他拆分成小问题,开始想的是用a[i][j]表示从i加到j的最大和,但是这样并没有化简,优化后仍是n*n的复杂度,所以我们要换种思路,以a[i]代表以i为结尾的最大子序列和,所以此时转移变成了a[i]=max(0,a[i-1])+a[i]。复杂度为n

代码

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
    int N,a[110],ans[110];
    while(cin>>N&&N)
    {
        for(int i=0;i<N;i++)
          cin>>a[i];
        memset(ans,0,sizeof(ans));
        ans[0]=a[0];
        for(int i=1;i<N;i++)
            ans[i]=max(0,ans[i-1])+a[i];
        int maxn=ans[0];
        for(int i=1;i<N;i++)
            maxn=max(maxn,ans[i]);
        cout<<maxn<<endl;
    }
    return 0;
}

例题:最长公共上升子序列

所得序列应该既满足公共子序列,又满足上升子序列,所以只需要在公共子序列前提下求出上升子序列既可以

代码:复杂度为n的3次方

#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;
int a[maxn],b[maxn],s[maxn][maxn];
int main()
{
    int N,M;
    while(cin>>N>>M&&M)
    {
        memset(s,0,sizeof(s));
        for(int i=1; i<=N; i++)
            cin>>a[i];
        for(int j=1; j<=M; j++)
            cin>>b[j];
        for(int i=1; i<=N; i++)
        {
            for(int j=1; j<=M; j++)
            {
                if(a[i]==b[j])
                {
                    for(int k=0; k<j; k++)
                        if(b[k]<a[i])//保证上升
                            s[i][j]=max(s[i-1][j],s[i-1][k]+1);
                }
                else//因为这个地方不用处理公共,所以就算是上升也不用管
                    s[i][j]=max(s[i-1][j],s[i][j-1]);
            }
        }
        //如果只求最长公共子序列
        //应改为
//        for(int i=1; i<=N; i++)
//        {
//            for(int j=1; j<=M; j++)
//            {
//                if(a[i]==b[j])
//                    s[i][j]=s[i-1][j-1]+1;
//                else
//                    s[i][j]=max(s[i-1][j],s[i][j-1]);
//            }
//        }
        int maxx=0;
        for(int j=1; j<=M; j++)
            maxx=max(maxx,s[N][j]);
        cout<<maxx<<endl;
    }
    return 0;
}

改进:第三个循环是为了求出a[i-1][k]的最大值,所以只需要一个常数代替既可以,复杂度为n*n

#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;
int a[maxn],b[maxn],s[maxn][maxn];
int main()
{
    int N,M;
    while(cin>>N>>M&&M)
    {
        memset(s,0,sizeof(s));
        for(int i=1; i<=N; i++)
            cin>>a[i];
        for(int j=1; j<=M; j++)
            cin>>b[j];
        for(int i=1; i<=N; i++)
        {
            int ma=0;//求s[i-1][k]的最大值
            for(int j=1; j<=M; j++)
            {
                 if(a[i]==b[j])
                    s[i][j]=ma+1;
                 else
                    s[i][j]=max(s[i-1][j],s[i][j-1]);
                 if(a[i]>b[j])
                    ma=max(ma,s[i-1][j]);
            }
        }
        int maxx=0;
        for(int j=1; j<=M; j++)
            maxx=max(maxx,s[N][j]);
        cout<<maxx<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值