王道考研系列 计算机考研 ——机试指南(第二版) 笔记(二)

计算机机试,王道机试指南(第二版)笔记

文章太长了,dp放这了 机试指南一些笔记。。。 题目代码github链接 https://github.com/BenedictYoung

链接


第十二章 动态规划(DP)

思想
在这里插入图片描述

分类
在这里插入图片描述

12.1 递归求解

12.1.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;
}
  1. 朴素递归+记忆化
#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表示未计算过
}
  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],别打错了。

  1. 朴素递归,会超时
#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;
}

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值