【总结】01背包问题详解(有难度的习题讲解)

A.Subset Sums集合

分析:设dp[i][j]表示前i个数和为j的方案数,则:
dp[i][j]=dp[i-1][j]+dp[i-1][j-i] (j>=i,dp[n][0]=1)
下面给出两种实现方法(推荐第一种):

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=5005;
ll n,m,dp[maxn];
int main() {
    
    
	scanf("%lld",&n);
	m=n*(n+1)/2;
	if(m&1) {
    
    
		printf("0");
		return 0;
	}
	m/=2;
	dp[0]=1;
	for(int i=1;i<=n;i++) {
    
    
		for(int j=m;j>=i;j--) {
    
    
			dp[j]+=dp[j-i];
		}
	}
	printf("%lld",dp[m]/2);
}

玄学做法:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=5005;
int n,m,dp[40][maxn];
int main() {
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++) m+=i;
	if(m&1) {
    
    
		printf("0");
		return 0;
	}
	m/=2;
	dp[0][0]=1;
	for(int i=1;i<=n;i++) {
    
    
		for(int j=1;j<=m;j++) {
    
    
			if(j<i) dp[i][j]=dp[i-1][j];
			else dp[i][j]=dp[i-1][j]+dp[i-1][j-i];
		}
	}
	printf("%d",dp[n][m]);
}

B.黄金树

分析:
// 设dp[i][j]表示前i天砍前j个树的最大利润
//若砍的树一定,那么一定是增长率大的最后砍
//所以我们选择排序(为了让方案变得有序)
//若砍j,则dp[i][j]=dp[i-1][j-1]+w[j]+(i-1)*k[j]
//若不砍j,则dp[i][j]=dp[i][j-1]
//(j是前i个中k[j]最大的,若不在第j天砍,则算出来一定不是最优,即该方案小于上方案)
//本题想到了不砍j时有可能在前面砍,这样就不能用dp[i][j-1]表示
//而排序则避免了这种决策(未想到)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std; 
const int maxn=255;
int n,m,dp[maxn][maxn];
struct node{
    
    
	int w,k;
}a[maxn];
bool cmp(node x,node y) {
    
    
	return x.k<y.k;
}
int main() {
    
    
	int t;
	scanf("%d",&t);
	while(t--) {
    
    
		scanf("%d%d",&n,&m);
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++) scanf("%d",&a[i].w);
		for(int i=1;i<=n;i++) scanf("%d",&a[i].k);
		sort(a+1,a+1+n,cmp);
		for(int i=1;i<=min(m,n);i++) {
    
    
			for(int j=i;j<=n;j++) {
    
    
				dp[i][j]=max(dp[i-1][j-1]+a[j].w+(i-1)*a[j].k,dp[i][j-1]);
			}
		}
		printf("%d\n",dp[min(m,n)][n]);
	}
}

C.差最小

分析:和背包问题没有什么关系。
dp[i][j]表示以第i个数为结尾,取出序列长度为j的方案数
答案取max。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=305;
int n,c,ans,a[maxn],dp[maxn][maxn];
int main() {
    
    
	memset(dp,0x3f3f3f3f,sizeof(dp));
	ans=0x3f3f3f3f;
	scanf("%d%d",&n,&c);
	for(int i=1;i<=n;i++) {
    
    
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++) {
    
    
		for(int j=1;j<=c&&j<=i;j++) {
    
    
			if(j==1) dp[i][j]=0;
			else {
    
    
				for(int k=1;k<i;k++) {
    
    
					dp[i][j]=min(dp[i][j],dp[k][j-1]+abs(a[i]-a[k]));
				}
			}
			if(j==c) ans=min(ans,dp[i][j]);
		}
	}
	printf("%d",ans);
}

D.夏季特惠【第五周】

分析:
这是一道典型的01背包的变形,总容量为x,关键在于如何求出每个物品所占用的空间(价值已确定)
设每个物品花费bi,优惠ci,则sum ci>=sum bi-x
变形得sum(bi-ci)<=x
bi-ci=bi-(ai-bi)=2*bi-ci, 这就是每个物品占用的空间,其价值为wi,背包容量为x

注意:2*bi-ci可能为负,我们可以提前把它装进背包里(背包总容量增加)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std; 
const int maxn=1e6+5,size=505;
ll n,x,dp[maxn],ans,cnt;
struct node{
    
    
	ll v,w;
}a[size];
int main() {
    
    
	scanf("%lld%lld",&n,&x);
	for(int i=1;i<=n;i++) {
    
    
		ll ai,bi,vi,wi;
		scanf("%lld%lld%lld",&ai,&bi,&wi);
		vi=bi*2-ai;
		if(vi<=0) {
    
    
			x-=vi;
			ans+=wi;
	    }
	    else {
    
    
	    	a[++cnt].v=vi;
	    	a[cnt].w=wi;
		}
	}
	for(int i=1;i<=cnt;i++) {
    
    
		for(int j=x;j>=a[i].v;j--) {
    
    
			dp[j]=max(dp[j],dp[j-a[i].v]+a[i].w);
		}
	}
	printf("%lld",dp[x]+ans);
}

猜你喜欢

转载自blog.csdn.net/cqbzlydd/article/details/106302331