计算机机试,王道机试指南(第二版)笔记
文章太长了,dp放这了 机试指南一些笔记。。。 题目代码github链接 https://github.com/BenedictYoung文章目录
第十二章 动态规划(DP)
思想
分类
12.1 递归求解
12.1.1 斐波那契数列
- 朴素递归求解,非常低效
int Fibonaccil(int n){
int answer;
if(n==0||n==1){
answer=n;
}else{
answer=Fibonaccil(n-1)+Fibonaccil(n-2);
}
return answer;
}
- 朴素递归+记忆化
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=100;
int memo[MAXN];
int Fibonaccil(int n){
if(memo[n]!=-1){
return memo[n];
}
int answer;
if(n==0||n==1){
answer=n;
}else{
answer=Fibonaccil(n-1)+Fibonaccil(n-2);
}
memo[n]=answer;
return answer;
}
int main(){
memset(memo,-1.sizeof(memo));//-1表示未计算过
}
- 数组递推
int fib[MAXN];
int Fibonacci(int n){
for(int i=0;i<=n;i++){
int answer;
if(i==0||i==1){
answer=n;
}else{
answer=fib[i-1]+fib[i-2];
}
fib[i]=answer;
}
return fib[n];
}
12.1.1.1 例题12.2 N阶楼梯上楼问题(华中科技大学复试上机题)
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=90+10;
int arr[MAXN];
int main(){
int N;
scanf("%d",&N);
for(int i=1;i<=N;i++){
if(i==1||i==2){
arr[i]=i;
}else{
arr[i]=arr[i-1]+arr[i-2];
}
}
printf("%d",arr[N]);
return 0;
}
12.1.1.2 习题12.1 吃糖果(北京复试上机题)
题目链接
代码同上
12.2 最大连续子序列和
12.2.1 例题12.2 最大序列和(清华大学复试上机题)
Tip:注意,在遍历dp获取最大值的时候,要把maximum置为-INF,因为结果可能是负数。
此外,遍历的时候循环内是dp[i],不是dp[n],别打错了。
- 朴素递归,会超时
#include <iostream>
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;
const int MAXN=1e6+10;
const int INF = INT_MAX;
long long arr[MAXN];
long long Fun1(int n){
long long answer;
if(n==0){
answer=arr[n];
}else{
answer=max(arr[n],Fun1(n-1)+arr[n]);
}
return answer;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;i++){
scanf("%lld",&arr[i]);
}
long long maximum=-INF;
for(int i=0;i<n;i++){
maximum=max(maximum,Fun1(i));
}
printf("%d\n",answer);
}
return 0;
}
2.递归+记忆化
#include <iostream>
#include <cstdio>
#include <climits>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=1e6+10;
const int INF = INT_MAX;
long long memo[MAXN];
long long arr[MAXN];
long long Fun2(int n){
if(memo[n]!=-1){
return memo[n];
}
long long answer;
if(n==0){
answer=arr[n];
}else{
answer=max(arr[n],Fun2(n-1)+arr[n]);
}
memo[n]=answer;
return answer;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;i++){
scanf("%lld",&arr[i]);
}
memset(memo,-1,sizeof(memo));
long long answer=-INF;
for(int i=0;i<n;i++){
answer=max(answer,Fun2(i));
}
printf("%lld\n",answer);
}
return 0;
}
3.递推
#include <iostream>
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;
const int MAXN=1e6+10;
const int INF = INT_MAX;
long long arr[MAXN];
long long dp[MAXN];
void Fun3(int n){
for(int i=0;i<n;i++){
long long answer;
if(i==0){
answer=arr[i];
}else{
answer=max(arr[i],dp[i-1]+arr[i]);
}
dp[i]=answer;
}
return;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;i++){
scanf("%lld",&arr[i]);
}
fill(dp,dp+n,-1);
long long maximum=-INF;
Fun3(n);
for(int i=0;i<n;i++){
maximum=max(maximum,dp[i]);
}
printf("%lld\n",maximum);
}
return 0;
}
小结
12.2.2 例题12.3 最大序列和(清华大学复试上机题)
分析
#include <iostream>
#include <cstdio>
#include <climits>
using namespace std;
const int MAXN=100+10;
const int INF=INT_MAX;
int matrix[MAXN][MAXN];
int total[MAXN][MAXN];
int arr[MAXN];
int dp[MAXN];
int maxSubSequence(int n){
int maximum=-INF;
for(int i=0;i<n;i++){
if(i==0){
dp[i]=arr[i];
}else{
dp[i]=max(dp[i-1]+arr[i],arr[i]);
}
}
for(int i=0;i<n;i++){
maximum=max(maximum,dp[i]);
}
return maximum;
}
int maxSubMatrix(int n){
int maximum=-INF;
for(int i=0;i<n;i++){
for(int j=i;j<n;j++){
for(int k=0;k<n;k++){
if(i==0){//注意判断条件是i==
arr[k]=total[j][k];
}else{
arr[k]=total[j][k]-total[i-1][k];
}
}
maximum=max(maximum,maxSubSequence(n));
}
}
return maximum;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
scanf("%d",&matrix[i][j]);
}
}
//sum in a line
//total[i][j]=sum(arr[k][j]) for k in [0,i]
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(i==0){
total[i][j]=matrix[i][j];
}else{
total[i][j]=total[i-1][j]+matrix[i][j];
}
}
}
int answer=maxSubMatrix(n);
printf("%d\n",answer);
}
return 0;
}
12.2.3 习题12.2 最大连续子序列(浙江大学复试上机题)
只需要加一个记录长度的数组即可
#include <iostream>
#include <cstdio>
#include <climits>
using namespace std;
const int MAXN=10000+10;
const int INF=INT_MAX;
int arr[MAXN];
int dp[MAXN];
int len[MAXN];
int main(){
int K;
while(scanf("%d",&K)!=EOF){
if(K==0){
break;
}
for(int i=0;i<K;i++){
scanf("%d",&arr[i]);
}
for(int i=0;i<K;i++){
if(i==0){
dp[i]=arr[0];
len[i]=1;
}else{
if(arr[i]<dp[i-1]+arr[i]){
dp[i]=dp[i-1]+arr[i];
len[i]=len[i-1]+1;
}else{
dp[i]=arr[i];
len[i]=1;
}
}
}
int maximum=-INF;
int maxIndex=0;
for(int i=0;i<K;i++){
if(maximum<dp[i]){
maximum=dp[i];
maxIndex=i;
}
}
if(maximum<0){
printf("%d %d %d\n",0,arr[0],arr[K-1]);
}else{
printf("%d %d %d\n",maximum,arr[maxIndex-len[maxIndex]+1],arr[maxIndex]);
}
}
return 0;
}
12.3 最长递增子序列
分析
12.3.1 例题 Longest Ordered Subsequence(POJ 2533)
#include <iostream>
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;
const int MAXN=1000+10;
int arr[MAXN];
//递归代码 recursive codes,非常低效
int Fun1(int n){
int answer=0;
if(n==0){
answer=1;
}else{
answer=1;
for(int i=0;i<n;i++){
if(arr[i]<arr[n]){
answer=max(answer,Fun1(i)+1);
}
}
}
return answer;
}
//递归+记忆化(空间换时间)
int memo[MAXN];
int Fun2(int n){
if(memo[n]!=-1){
return memo[n];
}
int answer=0;
if(n==0){
answer=1;
}else{
answer=1;
for(int i=0;i<n;i++){
if(arr[i]<arr[n]){
answer=max(answer,Fun1(i)+1);
}
}
}
memo[n]=answer;
return answer;
}
//递推
int dp[MAXN];
void Fun3(int n){
for(int i=0;i<n;i++){
int answer;
if(i==0){
answer=1;
}else{
answer=1;
for(int j=0;j<i;j++){
if(arr[j]<arr[i]){
answer=max(answer,dp[j]+1);
}
}
}
dp[i]=answer;
}
return;
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&arr[i]);
}
fill(dp,dp+MAXN,-1);
int maximum=0;
Fun3(n);
for(int i=0;i<n;i++){
maximum=max(maximum,dp[i]);
}
printf("%d\n",maximum);
return 0;
}
查找过程可以进行优化
定义辅助数组,按照dp[i]递增的顺序进行排列,support[i]中i索引为dp[i]的值,对应元素为取到dp[i]的数(value)。如support[2]=3,dp[i]取2的数字(value)有7,3,3<7,因此support[2]存储3。>
代码
#include <iostream>
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;
const int MAXN=1000+10;
const int INF=INT_MAX;
int arr[MAXN];
int support[MAXN];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&arr[i]);
}
support[0]=-INF;
int length=0;
for(int i=0;i<n;i++){
if(support[length]<arr[i]){
support[++length]=arr[i];
}else{
int position=lower_bound(support,support+length,arr[i])-support;
support[position]=arr[i];
}
}
printf("%d",length);
return 0;
}
12.3.2 例题12.4 拦截导弹(北京大学复试上机题)
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=25;
int height[MAXN];
int dp[MAXN];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&height[i]);
}
for(int i=0;i<n;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(height[i]<=height[j]){
dp[i]=max(dp[i],dp[j]+1);
}
}
}
int maximum=1;
for(int i=0;i<n;i++){
maximum=max(maximum,dp[i]);
}
printf("%d\n",maximum);
return 0;
}
12.3.3 例题12.4 合唱队形(北京大学复试上机题)
思路:思路:
动态规划:正反两次运用LIS
求出以每一个点结尾的从前往后最长递增子序列 dp1[i]dp1[i]
以每一个结点结尾的从后往前最长递增子序列 dp2[i]dp2[i]
当dp1[i] + dp2[i]dp1[i]+dp2[i]最大时,记作ansans,表示剩下的同学排成合唱队形最多的人数
则有n - ans + 1n−ans+1为当前状态下最少需要出列的同学人数(因为i这个位置被重复计算了一次,故需要+1)
/*
LIS最长递增子序列
状态方程:
dp[i] = max{dp[i], dp[j] + 1} j <= i && A[j] < A[i]
边界:
dp[i] = 1(1 <= i <= n)
*/
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 300;
int main(){
int n;
while(~scanf("%d", &n)){
int dp1[maxn];//左边最大升序子序列的长度
int dp2[maxn];//右边最长降序子序列的长度
int A[maxn];
for(int i = 0; i < n; i++){
scanf("%d", &A[i]);//输入
}
/*从前往后寻找以i点为尾的最长递增子列*/
for(int i = 0; i < n; i++){
dp1[i] = 1;/*每点为尾的子列长度最小都为1*/
for(int j = 0; j < i; j++){
if(A[j] < A[i]){
dp1[i] = max(dp1[i], dp1[j] + 1);
}
}
}
/*从后往前寻找以i点为尾的最长递增子列*/
for(int i = n - 1; i >= 0; i--){
dp2[i] = 1;/*每点为尾的子列长度最小都为1*/
for(int j = n - 1; j > i; j--){
if(A[j] < A[i]){
dp2[i] = max(dp2[i], dp2[j] + 1);
}
}
}
int ans = 1;
/*寻找点i两个子列和的最大值*/
for(int i = 0; i < n; i++){
if(dp1[i] + dp2[i] > ans){
ans = dp1[i] + dp2[i];
}
}
printf("%d\n", n - ans + 1);//重复减了自身两次,故加1
}
return 0;
}
12.4 最长公共子序列
递推公式
例题 11.6 Common Subsequence
poj-1458
题目链接
朴素递归策略
#include <iostream>
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;
const int MAXN=1000+10;
char str1[MAXN];
char str2[MAXN];
int Fun1(int n,int m){
int answer;
if(n==0||m==0){
answer=0;
}else{
if(str1[n]==str2[m]){
answer=Fun1(n-1,m-1)+1;
}else{
answer=max(Fun1(n-1,m),Fun1(n,m-1));
}
}
return answer;
}
int main(){
while(scanf("%s%s",str1+1,str2+1)!=EOF){
int n=strlen(str1+1);
int m=strlen(str2+1);
int maximum=Fun1(n,m);
printf("%d\n",maximum);
}
}
递归+记忆化
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN=1000+10;
char str1[MAXN];
char str2[MAXN];
int Fun1(int n,int m){
int answer;
if(n==0||m==0){
answer=0;
}else{
if(str1[n]==str2[m]){
answer=Fun1(n-1,m-1)+1;
}else{
answer=max(Fun1(n-1,m),Fun1(n,m-1));
}
}
return answer;
}
int memo[MAXN][MAXN];
int Fun2(int n,int m){
if(memo[n][m]!=-1){
return memo[n][m];
}
int answer;
if(n==0||m==0){
answer=0;
}else{
if(str1[n]==str2[m]){
answer=Fun2(n-1,m-1)+1;
}else{
answer=max(Fun2(n-1,m),Fun2(n,m-1));
}
}
memo[n][m]=answer;
return answer;
}
int main(){
while(scanf("%s%s",str1+1,str2+1)!=EOF){
int n=strlen(str1+1);
int m=strlen(str2+1);
//recursive
int maximum=Fun1(n,m);
//recursive + memory
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
memo[i][j]=-1;
}
}
maximum=Fun2(n,m);
printf("%d\n",maximum);
}
}
递推策略
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN=1000+10;
char str1[MAXN];
char str2[MAXN];
int Fun1(int n,int m){
int answer;
if(n==0||m==0){
answer=0;
}else{
if(str1[n]==str2[m]){
answer=Fun1(n-1,m-1)+1;
}else{
answer=max(Fun1(n-1,m),Fun1(n,m-1));
}
}
return answer;
}
int memo[MAXN][MAXN];
int Fun2(int n,int m){
if(memo[n][m]!=-1){
return memo[n][m];
}
int answer;
if(n==0||m==0){
answer=0;
}else{
if(str1[n]==str2[m]){
answer=Fun2(n-1,m-1)+1;
}else{
answer=max(Fun2(n-1,m),Fun2(n,m-1));
}
}
memo[n][m]=answer;
return answer;
}
int dp[MAXN][MAXN];
void Fun3(int n,int m){
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
int answer;
if(i==0||j==0){
answer=0;
}else{
if(str1[i]==str2[j]){
answer=dp[i-1][j-1]+1;
}else{
answer=max(dp[i-1][j],dp[i][j-1]);
}
}
dp[i][j]=answer;
}
}
return;
}
int main(){
while(scanf("%s%s",str1+1,str2+1)!=EOF){
int n=strlen(str1+1);
int m=strlen(str2+1);
//recursive
int maximum=Fun1(n,m);
//recursive + memory
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
memo[i][j]=-1;
}
}
maximum=Fun2(n,m);
//recurrent
Fun3(n,m);
maximum=dp[n][m];
printf("%d\n",maximum);
}
}
采用字符数组
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
const int MAXN=100+10;
char s1[MAXN];
char s2[MAXN];
int dp[MAXN][MAXN];
int main(){
while(scanf("%s%s",s1+1,s2+1)!=EOF){//注意从下标1开始输入
int n=strlen(s1+1);
int m=strlen(s2+1);
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(i==0||j==0){//边界情况初始化(空串匹配)
dp[i][j]=0;
continue;
}
if(s1[i]==s2[j]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
printf("%d\n",dp[n][m]);
}
return 0;
}
习题 12.4 Coincidence(上海交通大学复试上机题)
Tip:可以采用C++ string,但是要注意代码书写变动。或者直接采用上面代码
题目链接
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
const int MAXN=100+10;
int dp[MAXN][MAXN];
int lcs(string str1,string str2){
int n=str1.length();
int m=str2.length();
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(i==0||j==0){
dp[i][j]=0;
continue;
}
if(str1[i-1]==str2[j-1]){//注意这里是i-1,j-1,因为要考虑空串,因此i,j代表考虑str1,第i个字符,str2,第j个字符
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[n][m];
}
int main(){
string str1,str2;
while(cin>>str1>>str2){
cout<<lcs(str1,str2)<<endl;
}
return 0;
}
12.5 背包问题
12.5.1 0-1背包
递推式
例题 12.7 点菜问题
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=1000+10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXN];
int main(){
int n,m;
while(scanf("%d%d",&m,&n)!=EOF){
for(int i=1;i<=n;i++){//这里一般采用下标为1开始,默认0为空集,什么都没有
scanf("%d%d",&weight[i],&value[i]);
}
for(int i=0;i<=n;i++){
dp[i][0]=0;//背包容量为0,无法放任何物品
}
for(int j=0;j<=m;j++){
dp[0][j]=0;//什么都不放,价值也为0
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<weight[i]){//剩余容量不足
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
}
}
}
printf("%d\n",dp[n][m]);
}
return 0;
}
优化空间
依赖关系
可以只保存一行数据,因为每一行更新只需要上一行数据,但是要从后向前更新,防止前面数据被更新掉
更新后
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=1000+10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN];
int main(){
int n,m;
while(scanf("%d%d",&m,&n)!=EOF){
for(int i=1;i<=n;i++){//这里一般采用下标为1开始,默认0为空集,什么都没有
scanf("%d%d",&weight[i],&value[i]);
}
for(int j=0;j<=m;j++){
dp[j]=0;//什么都不放,价值也为0
}
for(int i=1;i<=n;i++){
for(int j=m;j>=weight[i];j--){//这里进行反向更新,要注意,此外这里对于j<weight[i]默认无需更新,因为剩余容量不足
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
printf("%d\n",dp[m]);
}
return 0;
}
习题 12.5 采药(北京大学复试上机题)
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=100+10;
const int MAXT=1000+10;
int cost[MAXN];
int value[MAXN];
int dp[MAXT];
int main(){
int T,M;
while(scanf("%d%d",&T,&M)!=EOF){
for(int i=0;i<M;i++){
scanf("%d%d",&cost[i],&value[i]);
}
for(int i=0;i<=T;i++){
dp[i]=0;
}
for(int i=0;i<M;i++){
for(int j=T;j>=cost[i];j--){
dp[j]=max(dp[j],dp[j-cost[i]]+value[i]);
}
}
printf("%d\n",dp[T]);
}
return 0;
}
习题 12.6 最小邮票数(清华大学复试上机题)
分析
每张邮票价值为1,求背包容量M,N个物品,装满的前提下获得最小的价值;
对于dp[i][j]来说,
(1)如果放入第i个物品可以填满j容量的背包,那么使用前i-1个物品可以填满j-w[i]容量的背包,于是dp[i][j]=dp[i-1][j-w[i]]+1;
(2)如果不放入第i个物品也可以填满j容量的背包,意味着只使用前i-1个物品仍然可以填满j容量的背包,dp[i][j]=dp[i-1][j]。
总结以上两种情况:dp[i][j]取其中较小的。
如果前i个物品不能存满背包,设置dp[i][j]为一个超过总价值的数,当更大的i,j需要使用较小的i,j的dp时,dp都不能存满背包,则较大的dp也不能存满背包。
边界条件:dp[0][j]=MAX,不存放物品时,任何非零容量都不能存满;dp[i][0]=0,容量为0,总是可以填满且总价值为0;
状态转移:dp[i][j]=min(dp[i-1][j-w[i]]+1,dp[i-1][j]);任何情况下,dp都不会超过设置的max值。
优化:可以看到dp[i][j]只和上一行的数据有关,简化成dp[j]=min(dp[j-w[i]]+1,dp[j]),且遍历到dp[j]时,dp[j-w[i]]值应还未修改,所以j从后向前遍历。
int main(){
#include <iostream>
#include <cstdio>
#include <climits>
using namespace std;
const int MAXN=20+10;
const int MAXM=100+10;
const int INF=100;
int value[MAXN];
int dp[MAXM];
int main(){
int M,N;
while(scanf("%d",&M)!=EOF){
scanf("%d",&N);
for(int i=0;i<N;i++){
scanf("%d",&value[i]);
}
for(int i=1;i<=M;i++){
dp[i]=INF;//INF代表不能凑出
}
dp[0]=0;//注意这里代表0总值的可以0张邮票凑出,这里不要忘记初始化
for(int i=0;i<N;i++){
for(int j=M;j>=value[i];j--){
if(dp[j-value[i]]!=INF){
dp[j]=min(dp[j],dp[j-value[i]]+1);
}
}
}
if(dp[M]==INF){
printf("0\n");
}else{
printf("%d\n",dp[M]);
}
}
return 0;
}
12.5.2 完全背包(物品无限)
状态转移方程
12.5.2.1 HDU 4508 减肥记I
hdu-4508
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN=1000+10;
const int MAXM=1e5+10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXM];
int main(){
int n,m;
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++){
scanf("%d%d",&value[i],&weight[i]);
}
scanf("%d",&m);
for(int i=0;i<=n;i++){
dp[i][0]=0;
}
for(int j=0;j<=m;j++){
dp[0][j]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<weight[i]){
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-weight[i]]+value[i]);//这里拿了还可以继续拿
}
}
}
printf("%d\n",dp[n][m]);
}
}
依赖关系
优化,更新只需要本行和上一行相同位置数据
注意:
0-1背包问题中,想要更新某个值的时候,该值所依赖的值,必须保证尚未更新的状态(依赖于上一行),因此采用反向更新,防止之前的值被覆盖。
完全背包中,想要更新某个值,必须保证所依赖的值已经更新过了(同一行),因此需要采取正向更新。
此外,要从weight[i]开始更新。
优化后
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN=1000+10;
const int MAXM=1e5+10;
int weight[MAXN];
int value[MAXN];
int dp[MAXM];
int main(){
int n,m;
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++){
scanf("%d%d",&value[i],&weight[i]);
}
scanf("%d",&m);
for(int j=0;j<=m;j++){
dp[j]=0;
}
for(int i=1;i<=n;i++){
for(int j=weight[i];j<=m;j++){//这里从weight[i]开始正向更新,道理同上
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);//这里拿了还可以继续拿
}
}
printf("%d\n",dp[m]);
}
}
例题 12.8 Piggy-Bank
“凑”的问题
#include <iostream>
#include <cstdio>
#include <climits>
using namespace std;
const int INF=INT_MAX/10;
const int MAXN=10000;
int dp[MAXN];
int v[MAXN];//物品价值
int w[MAXN];//物品重量
int main(){
int caseNumeber;
scanf("%d",&caseNumeber);
while(caseNumeber--){
int e,f;
scanf("%d%d",&e,&f);
int m=f-e;//背包容量
int n;//物品种类
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d%d",&v[i],&w[i]);
}
for(int i=1;i<=m;i++){
dp[i]=INF;//注意初始化
}
dp[0]=0;//!!!!
for(int i=0;i<n;i++){
for(int j=w[i];j<=m;j++){
dp[j]=min(dp[j],dp[j-w[i]]+v[i]);
}
}
if(dp[m]==INF){
printf("This is impossible\n");
}else{
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[m]);
}
}
return 0;
}
小结
总之,完全背包问题的特点是每类物品可选的数量为无穷,其解法与0-1背包问题整体保持一致,与其不同的仅为状态更新时的遍历顺序。
12.5.2 多重背包(物品数目有限)
将同个物品进行绑定,再利用0-1背包进行求解
这样可以组合出任意0-物品数目的情况,从而可以获取任意物品数目范围内任意指定的同个商品及价值。
例题 12.9 珍惜现在,感恩生活
Hoj-2191
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=1000+10;
int weight[MAXN];
int value[MAXN];
int amount[MAXN];
int dp[MAXN];
int newWeight[20*MAXN];//拆分物品价值和重量
int newValue[20*MAXN];
int main(){
int caseNumber;
scanf("%d",&caseNumber);
while(caseNumber--){
int n,m;
scanf("%d%d",&m,&n);
int number=0;
for(int i=1;i<=n;i++){//这里一般采用下标为1开始,默认0为空集,什么都没有
scanf("%d%d%d",&weight[i],&value[i],&amount[i]);
for(int j=1;j<=amount[i];j*=2){//按照2的次幂数目进行拆分
number++;
newWeight[number]=weight[i]*j;
newValue[number]=value[i]*j;
amount[i]-=j;
}
if(amount[i]>0){//剩下的物品也绑定起来
number++;
newWeight[number]=weight[i]*amount[i];
newValue[number]=value[i]*amount[i];
}
}
//改造成0-1背包问题
for(int j=0;j<=m;j++){
dp[j]=0;//什么都不放,价值也为0
}
for(int i=1;i<=number;i++){
for(int j=m;j>=newWeight[i];j--){//这里进行反向更新,要注意,此外这里对于j<weight[i]默认无需更新,因为剩余容量不足
dp[j]=max(dp[j],dp[j-newWeight[i]]+newValue[i]);
}
}
printf("%d\n",dp[m]);
}
return 0;
}
12.6 其他问题
12.6.1 The Triangle
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=100;
int dp[MAXN][MAXN];
int matrix[MAXN][MAXN];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
scanf("%d",&matrix[i][j]);
dp[i][j]=matrix[i][j];
}
}
for(int i=n-1;i>=0;i--){
for(int j=0;j<=i;j++){
dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
}
}
printf("%d\n",dp[0][0]);
return 0;
}
12.6.2 习题 12.7 放苹果(北京大学复试上机题)
思路:
https://blog.csdn.net/csyifanZhang/article/details/105652758模板
#define ll int
#define vec vector<ll>
#define MAX 15
#define inf 0x3fffffff
ll dp[MAX][MAX];
int main() {
ll M, N;
while (cin >> M >> N) {
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i = 1; i <= N; i++) {
//j从零开始,也就是每行的第一列都是1 dp[i][0]=1 因为盘子可以为空
for (int j = 0; j <= M; j++) {
if (j >= i)dp[i][j] = dp[i - 1][j] + dp[i][j - i];
else dp[i][j] = dp[i - 1][j];
}
}
cout << dp[N][M] << endl;
}
}
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN=20+10;
int dp[MAXN][MAXN];
int main(){
int m,n;
while(scanf("%d%d",&m,&n)!=EOF){
memset(dp,0,sizeof(dp));
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
if(i==0){
dp[i][j]=1;
}
if(j<=i){
dp[i][j]=dp[i-j][j]+dp[i][j-1];
}else{
dp[i][j]=dp[i][j-1];
}
}
}
printf("%d\n",dp[m][n]);
}
}
12.6.3 习题 12.8 整数划分(清华大学复试上机题)
#include<iostream>
using namespace std;
const int mod=1e9,MAX=1e6+10;
int f[MAX];
int main(){
int n;
f[0]=1;
while(~scanf("%d",&n)){
for(int i=1;i<=n;i*=2){
for(int j=i;j<=n;++j){
f[j]=(f[j]+f[j-i])%mod;
}
}
printf("%d\n",f[n]);
}
return 0;
}