P2347 洛谷题解

137 阅读3分钟

写题解!!!

题目传送门

题目类型

背包DP,多重背包问题

(但是里面也用到了到达型背包的思想)


解题思路

状态设计:dp[k]==1为当使用j个重量为第i个砝码的重量时,可以实现重量为k

使用三重循环实现

  • 第一层枚举第i个砝码的重量(i从1到6)
  • 第二层枚举用重量为w[i](w数组记录砝码重量)的砝码的个j(j从1到a[i](a数组记录砝码个数))
  • 第三层枚举重量k(k从1000到0倒序循环,不能正序!!!本人多次被着个问题困扰,后来发现只是滚动数组优化,要是正序就成完全背包了,一个砝码被使用无数次)

转移方程:if(dp[k]==1 && k+w[i]<=1000) dp[k+w[i]]=1;


另一种思路:

将第二层循环和第三层循环换一下位置

状态设计:dp[j]==1为当使用k个重量为第i个砝码的重量时,可以实现重量为j

转移方程改为if(dp[j]==1 && j+w[i]<=1000) dp[j+w[i]*k]=1;

核心代码:

for(int i=1;i<=6;i++) {
		for(int j=1000;j>=0;j--){
			for(int k=1;k<=a[i];k++) {
				if(dp[j]==1 && j+w[i]<=1000)
				dp[j+w[i]*k]=1;
			}
		}
	}

这样就会先枚举重量,在枚举砝码个数,所以需要将dp[k+w[i]]=1;改为dp[j+w[i]*k]=1;

在法1中,砝码是一个个累加上去的,dp[k+w[i]]=1其实是dp[k+w[i]*1]=1;

相当于你先取第一个砝码算出可达到的重量,在成立重量上加上w[i],再取第二个砝码,当你找到一个成立重量时,这些成立重量中,包含了上一轮时找到的新的成立重量,在上面继续累加,直到j等于a[i]


在法2中,个数在递增,每次要在k上加一个w[i]*j;

这样k个重量为w[i]的砝码就会一次性在这些成立重量上累加完,下一次循环就会是新的重量

个人感觉法2更好理解

一些新的想法

如果我让k从0开始循环……

这样做在法2中是成立的,因为这相当于不取第i个砝码,k增加w[i]*j,增加0;

在法1中却不行,因为你没有取第i个砝码,可是j却增加了w[i]……


附上AC代码(法1):

#include<iostream>
using namespace std;
int a[10],dp[1500],w[10]={0,1,2,3,5,10,20};
int ans=0;
int main() {
	dp[0]=1;
	for(int i=1;i<=6;i++) cin>>a[i];
	for(int i=1;i<=6;i++) {
		for(int j=1;j<=a[i];j++) {
			for(int k=1000;k>=0;k--){
				if(dp[k]==1 && k+w[i]<=1000)
				dp[k+w[i]]=1;
			}
		}
	}
	for(int i=1;i<=1000;i++) {
		if(dp[i]==1) ans++;
	}
	printf("Total=%d",ans);
	return 0;
}

注:

  1. 在给数组赋初始值时,记得把第一个值设为0,否则会wa!!!(不要问我怎么知道的……)
  1. 最后统计重量个数时,要从1开始循环,因为一个砝码也不取得情况不成立。
  1. dp[0]记得设为1,设一个砝码都不取的情况已有,否则不管输多大的数进去,也只会输出0……

请各位点个赞支持一下