dp-0/1背包变题

一、最简单的求两个子集和差值最小

题目链接:https://www.lintcode.com/problem/724/
变相的0/1背包问题
这个题考一点思维能力,两部分的差值绝对值最小,那就要让两部分的和最接近,最理想的状态肯定是两部分和刚好相等,差值为0。
但是如果满足不了,则尽可能往那儿靠,就是在背包容量为sum/2的时候让S1最大化地装东西,这就转化成了0/1背包问题
AC代码:

#include<bits/stdc++.h>
using namespace std;
int num[1000000];
int n;
int main()
{
	cin>>n;
	int sum=0;
	for(int i=0;i<n;i++) 
	{
		cin>>num[i];sum+=num[i];
	}
	int c=sum/2;
	//cout<<c<<endl;
	int dp[n+1][c+1];
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=c;j++)
		{
			if(num[i-1]<=j)
			{
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-num[i-1]]+num[i-1]);
			}
			else dp[i][j]=dp[i-1][j];
		}
	} 
	int res=dp[n][c];
	cout<<abs(res-(sum-res));
	return 0;
}
/*
dp[i][j] -->将前i个物品放入容量为j的背包能得到的最大和 
*/

二、求两个子集和相等

题目链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/

  1. nums如果要想有两个子序列相等,那么sum一定是偶数,奇数肯定不能分成两个相等序列。
  2. 跟上面的思路类似,sum/2 可以看作背包最大容量,nums[i] 可以看作物品质量和价值(相等),那么这道题就可以转换为每件物品只能用一次,哪些物品装入背包里物品价值总和最大,由于物品质量和价值相等,所以其实尽量装满背包,价值总和就是最大。(0-1 背包问题)
  3. 判断最大价值是否等于 sum/2, 成立则返回 true, 反之返回 false。
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=0;
        int n=nums.size();
        for(int i=0;i<n;i++) 
        {
            sum+=nums[i];
        }
        if(sum%2) return false;  //如果和为奇数,肯定不能分割成两个相等和的子集
        int dp[n+1][sum/2+1];
        memset(dp,0,sizeof(dp));
        for(int j=1;j<=sum/2;j++)
        {
            for(int i=1;i<=n;i++)
            {
                if(nums[i-1]<=j)
                {
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
                }
                else dp[i][j]=dp[i-1][j];
            }
        }
        return dp[n][sum/2]==sum/2;
    }
};

三、是否存在两个子集的和相等并且和最大

题目链接:http://lx.lanqiao.cn/problem.page?gpid=T2992

  1. 这个题既用到了动态规划,也用到的贪心思想
  2. 我首要想到的就是简单的回溯,结果运行超时了,转变一下,将木棍的长度看成一个集合,其实就是在一个集合中找两个子集,使得这两个子集的和相等且最大,和相等就是上一个题的dp思路,和最大采用贪心思想,将集合元素排序后,每次判断当前集合元素和是否能找到两个子集的和相等,如果不能则删去集合中的最小的元素,再次判断剩下元素是否满足
  3. 找到的第一个满足条件的集合
    注意:sum为偶数不一定代表就满足条件,还需要进行判断,
    比如[2,3,4,5],sum=14,sum/2=7,dp[4][7]=5!=7,但是他的答案应该是[2,3] 和子集[5]

AC代码:

#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXSIZE 16
vector<int>num;
bool Deal(int n,int sum)
{
	
	if(sum%2) return false;
	
	int dp[n+1][sum/2+1];
	memset(dp,0,sizeof(dp));
	for(int j=1;j<=sum/2;j++)
	{
		for(int i=1;i<=n;i++)
		{
			if(num[i-1]<=j)
			{
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-num[i-1]]+num[i-1]);
			}
			else dp[i][j]=dp[i-1][j];
		}
	}
	return dp[n][sum/2]==sum/2;
}
int main()
{
	int n=0;
	cin>>n;
	for(int i=0;i<n;i++) 
	{
		int t;cin>>t;
		num.push_back(t);
	}
	sort(num.begin(),num.end()); 
	int sum=0;
	for(int i=0;i<n;i++) sum+=num[i];
	while(n>0)
	{
		if(Deal(n,sum))
		{
			cout<<sum/2;
			break;
		}
		for(int j=0;j<n;j++)
		{
			if((sum-num[j])%2==0) //把当前这个减去能成为偶数 
			{	
				sum-=num[j];
				n--;
				num.erase(num.begin()+j);	
				break;
			}
			
		}
	}
	if(n==0) cout<<0; 
	return 0;
	
}

三个题目类似的方法,类似的思想,比较学习

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值