dp入门

dp:动态规划算法

它针对满足特定条件的一类问题,对各状态维度进行分阶段、有顺序、无重复、决策性的遍历求解。

dp解题的一般思路:

  1. 把原问题分解为子问题
  2. 确定状态
  3. 确定一些初始状态(边界状态)的值
  4. 确定状态转移方程

能用dp解决的问题的特点:

  1. 问题具有最优子结构性质
  2. 无后效性
  3. 子问题可重叠性

例题:求最长上升子序列 HZNUOJ2012

思路:
1.找子问题
该题可转化为求以ak(k=1,2,3…N)为终点的最长上升子序列的长度
2.确定状态
子问题只和一个变量——数字的位置相关。因此序列中数的位置k就是“状态”,而状态k对应的“值”,就是以ak作为“终点”的最长上升子序列的长度。则状态一共有N个。
3.找出状态转移方程
maxLen(k)表示以ak作为“终点”的最长上升子序列的长度那么:

   初始状态:maxLen(1)=1
   maxLen(k)=max{ maxLen(i) : 1 <=i <k 且 a~i~ < a~k~ 且 k!=1 } + 1
   若是找不到这样的i,则maxLen(k)=1

例题代码如下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=1010; 
int maxLen[MAXN];
int a[MAXN];
int main()
{
	for(int i=1;i<=10;i++)
	{
		cin>>a[i];
		maxLen[i]=1;		//初始化 
	}
	for(int i=2;i<=10;i++)
	{
		//每次求第i个数为终点的最长上升子序列的长度 
		for(int j=1;j<i;j++)
		{
			//查看第j个数为终点的最长上升子序列 
			if(a[i]>a[j])
				maxLen[i]=max(maxLen[i],maxLen[j]+1);
		}
	}
	cout<<*max_element(maxLen+1,maxLen+10+1);
	//该函数在maxLen[1]到maxLen[10]之间寻找最大值
	return 0;
} 		//时间复杂度是O(n^2) 

此附max_element函数用法:max_element函数

DP的常用两种形式

  1. 递归型
    优点:直观,容易编写
    缺点:可能会因递归层数太深导致爆栈,函数调用带来额外的时间开销。无法使用滚动数组节省空间。总体来说,比递推型慢。
  2. 递推型
    效率高,有可能使用滚动数组节省空间。

例题:公共子序列
给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。

Sample Input
abcfbc abfcab
programming contest
abcd mnp

Sample Output
4
2
0

解题思路:
输入两个字符串s1,s2.
设MaxLen(i,j)表示:
s1的前i个字符形成的子串和s2的前j个字符形成的字串的最长公共子序列的长度(i,j从1开始算)
MaxLen(i,j)就是本题的“状态”。
题目就是要求求出 MaxLen(strlen(s1),strlen(s2))。

显然:
MaxLen(n,0) = 0
MaxLen(0,n) = 0
递推公式:
if(s1[i]==s2[j])
	MaxLen(i,j)=MaxLen(i-1,j-1)+1;
else
	MaxLen(i.j)=Max( MaxLen(i,j-1),MaxLen(i-1,j) );
//时间复杂度是O(mn), m,n是两个字符串长度。

A - Common Subsequence (其实就是公共子序列问题)

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int f[1100][1100];
char s1[1000], s2[1000];
int len1, len2;
int main()
{
    while(scanf("%s %s", s1, s2) != EOF)
    {
        len1 = strlen(s1);
        len2 = strlen(s2);
        memset(f, 0, sizeof(f));
        for(int i = 1;i <= len1; ++i)
        {
            for(int j = 1;j <= len2; ++j)
            {
                if(s1[i-1] == s2[j-1]) f[i][j] = f[i-1][j-1]+1;
                else
                {
                    f[i][j] = max(f[i-1][j], f[i][j-1]);
                }
            }
        }
        cout << f[len1][len2] << endl;
    }
    return 0;
    
	}

B - Super Jumping! Jumping! Jumping! 即最大上升子段和

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e5+10;
#define inf 0x3f3f3f3f;
int n;

int dp[MAXN];
int main()
{
    while(scanf("%d",&n)!=EOF&&n)
    {
    	int a[MAXN]={0};
    	for(int i=1;i<=n;i++)
    	{
    		cin>>a[i];
		}
		dp[0]=0;
		for(int i=1;i<=n;i++)
		{
			dp[i]=-inf;
			for(int j=0;j<i;j++)
			{
				if(a[i]>a[j])
					dp[i]=max(dp[i],dp[j]); 
			}
			dp[i]+=a[i];//记录i点前的最大子段和 
		}
		int ans=-inf;
		for(int i=1;i<=n;i++)
		{
			if(dp[i]>ans)
				ans=dp[i];
		}
		cout<<ans<<endl;
		
	}
    return 0;
    
}

C - 滑雪 (高度持续下降问题)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
int max(int x,int y)
{
	if(x>y) return x;
	return y;
}
int mp[110][110];
int dp[110][110];
int maxp=0;
int maxi,maxj;
int dx[]={0,0,0,1,-1};		//横坐标加减
int dy[]={0,1,-1,0,0};		//纵坐标加减
int r,c;
int dfs(int x,int y)		//递归深度搜索
{
	if(dp[x][y]!=0)
	{
		return dp[x][y];
	}
	for(int i=1;i<=4;i++)
	{
		if(mp[x][y]>mp[x+dx[i]][y+dy[i]]&&x+dx[i]>=1&&x+dx[i]<=r&&y+dy[i]>=1&&y+dy[i]<=c)
		{
			dp[x][y]=max(dp[x][y],dfs(x+dx[i],y+dy[i])+1);
		}
	}
	return dp[x][y];		//指坐标(x,y)的点能滑的最长长度
}
int main()
{
	scanf("%d %d",&r,&c);
	memset(dp,0,sizeof dp);		//将dp数组初始化为0
	for(int i=1;i<=r;i++)
	{
		for(int j=1;j<=c;j++)
		{
			scanf("%d",&mp[i][j]);		//输入各个点的高度
		}
	}
	int max1=0;
	for(int i=1;i<=r;i++)
	{
		for(int j=1;j<=c;j++)
		{
			max1=max(max1,dfs(i,j));
		}
	}
	printf("%d\n",max1+1);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值