DP基础题型总结(01背包和LIS以及LCS)

今天做了学长拉的LIS和LCS以及背包问题的专题,因此就在这做个总结。

首先讲一下关于背包问题,

背包中最关键的无非就是01背包,以及完全背包,而且这两个代码也都很好写,也很好理解(就循环遍历的顺序变了),除此之外还有多重背包,二维费用的背包,以及混合背包,但万变不离其宗,都是由01背包演变而来,而且大都可以变成01背包的形式。

关于背包问题的讲解,大家可以去网上搜一下dd大佬的背包九讲,这可是经典!    当然这也有链接:背包九讲

下面贴一些01背包以及多重背包的题型。

基础背包问题
hdu2602——01背包模板题解
hdu2191——多重背包模板题解
hdu2546——01背包理解题解
Robberies ——01背包变形(思维题)题解
hdu2639——01背包+第k优解问题题解
hdu1171-——多重背包理解题解

都是一些水题,不过可以加深一下自己看完背包九讲后的印象。

 


讲完背包问题就是LCS和LIS(两种实现)

先将LCS吧

LCS(longest common sequence) ,最长公共子序列,是一个比较经典的dp基础问题,也是很重要的,很多比赛都有从它变形出来的题,其中主要的题型就是记录和输出路径。

先上裸的代码

//递归写法,时间复杂度很不友好!!慎用。
//而且我们有爸爸妈妈都喜欢的递推写法,所以。。。 
#include <iostream>
#include <cstring>
using namespace std;
char str1[205],str2[205];
int len1,len2;
int maxlen(int i, int j)
{
	if(j == 0 || i == 0)//如果有一个为0,代表就没有相同的 
	   return 0;
	if(str1[i-1] == str2[j-1])//如果相同的话 
	   return maxlen(i-1,j-1)+1;
	else//不同的话,这个也很好想到。 
	   return max(maxlen(i-1,j),maxlen(i,j-1)); 
	   
}
int main(int argc, char** argv) {
	while(cin >> str1 >> str2)
	{
        len1 = strlen(str1);
		len2 = strlen(str2);
		cout<<maxlen(len1,len2)<<endl;
	}
	return 0;
}

 

//递推写法,比较经典. 
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std; 
char str1[20005],str2[20005];
int len1,len2;
int maxlen[2005][2005];
int main(int argc, char** argv) {
	while(cin >> str1 >> str2)
	{
		len1 = strlen(str1);
		len2 = strlen(str2);
		for(int i = 0;i <= len1; i++)
		  maxlen[i][0] = 0;
		for(int j = 0;j <= len2; j++)
		  maxlen[0][j] = 0;
		for(int i = 1 ;i <= len1; i++){
			for(int j = 1; j  <= len2; j++){
				if(str1[i-1]==str2[j-1]) maxlen[i][j] = maxlen[i-1][j-1] + 1;
			    else maxlen[i][j] = max(maxlen[i-1][j],maxlen[i][j-1]); 
			}
		} 
		cout << maxlen[len1][len2]<<endl;
	}
	return 0;
}

其实这个是比较好理解的,状态转移方程式

if(str1[i]==str2[j])

      dp[i][j]=dp[i-1][j-1]+1

else 

      dp[i][j]=max(dp[i][j-1],dp[i-1][j])

dp[i][j]表示在str1的前i个字母和str2的前j个字母里面有多少个相同的。

然后就是LIS,最长上升子序列(类似有下降,严格上升之类的)

首先抛出O(n*n)的写法

#include <iostream>
using namespace std;  
const int maxn = 100000 + 10;
int a[maxn];
int dp[maxn];
int main(int argc, char** argv) {
	int n;
	cin >> n;
	for(int i = 0; i < n; i++){
	    cin >> a[i];
    	dp[i]=1;
	} 
	  for(int i = 1; i < n; i++)
	     for(int j = 0; j < i; j++)	
	     	 if(a[j] < a[i] )
	     	   	dp[i]=max(dp[j]+1,dp[i]); 		 
	int maxlen=0;
	for(int i = 0; i  < n; i++)	 
	    if(maxlen<dp[i])
	       maxlen=dp[i];
    cout << maxlen <<endl;  
	return 0;
}

再来一个O(n*logn)的写法,其实就是二分一下

#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 50001;
int binary_search(int key, int *g, int low, int high)
{
    while (low < high)
    {
        int mid = (low + high) >> 1;
        if (key >= g[mid])
            low = mid + 1;
        else
            high = mid;
    }
    return low;
}
int main()
{
    int i, j, a[maxn], g[maxn], n, len;
    while (cin >> n)
    {
        memset(g, 0, sizeof(g));
        for (i = 0; i < n; i++)
            cin >> a[i];
        g[1] = a[0], len = 1;//初始化子序列长度为1,最小右边界
        for (i = 1; i < n; i++)
        {
            if (g[len] < a[i])//(如果允许子序列相邻元素相同 g[len]<=a[i],默认为不等)
                j = ++len; //a[i]>g[len],直接加入到g的末尾,且len++
            else
                j = binary_search(a[i], g, 1, len + 1); //可以用STL中的lower_bound; 
            g[j] = a[i];//二分查找,找到第一个比a[i]小的数g[k],并g[k+1]=a[i]
        }
        cout << len << endl;
    }
    return 0;
}

也可以借用STL中的lower_bound,

        int n;
	cin>>n;
	int dp[1005],a[1005];
	for(int i = 1;i<=n;++i){
		scanf("%d",&a[i]);
		dp[i]=1; 
	} 
	int k = 1;
	dp[k]=a[1];
	for(int i = 2;i<=n;++i){
		if(dp[k]<a[i]) dp[++k]=a[i];
		else	*lower_bound(dp+1,dp+1+k,a[i])=a[i];
	}
	printf("%d\n",k);

状态转移方程的话,dp[i]=max(dp[j]+1,dp[i])(a[j]<a[i]&&j<i);

dp[i]表示在前i个数里面有多少个上升的数。

LIS和LCS习题
最长公共子序列(LCS)模板题上面有。
poj2533——O(nlogn)LIS算法上面有。
poj1836——正逆两次LIS求最大值题解
hdu1160——二维LIS+输出路径题解
poj2250——LCS记录路径题解
hdu1503——LCS路径输出题解

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值