1.01背包问题
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
转移方程:dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i]
#include "stdio.h"
#define max(a,b) ((a)>(b)?(a):(b))
int main(){
int v = 10 ;
int n = 5 ;
int value[] = {0, 8 , 10 , 4 , 5 , 5};
int weight[] = {0, 6 , 4 , 2 , 4 , 3};
int i,j;
int dp[n+1][v+1];
for(i = 0; i < n+1; i++)
for(j = 0; j < v+1; j++)
dp[i][j] = 0;
for(i = 1; i <= n; i++){
for(j = 1; j <= v; j++){
if(j >= weight[i])
dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i]);
else
dp[i][j] = dp[i-1][j];
}
}
printf("%d",dp[n][v]);
}
完全背包
问题描述:有一个容积为V的背包,同时有n种物品,每种物品均有无数多个,并且每种物品的都有自己的体积和价值。求使用该背包最多能够装的物品价值总和。// 具体问题: 物品种数N=4,物品重量w[N+1]={0,2,3,4,7},物品价值V[N+1]={0,1,3,5,9},背包的最大容量为10.
总体思路:思路跟01背包差不多,分解问题,求分解问题的最优解,因为完全背包里面,每种物品是无限多的,所以每次更新数据的时候还是对这种物品进行装拿。
#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;
#define N 4
#define M 10
int date[N+1][M+1],w[N+1]={0,2,3,4,7},v[N+1]={0,1,3,5,9};
void DP()
{
int i,j;
for (i=1;i<=N;i++)
{
for (j=1;j<=M;j++)
{
if (j>=w[i])
date[i][j]=max(date[i-1][j],date[i][j-w[i]]+v[i]);
else
date[i][j]=date[i-1][j];
printf("%4d",date[i][j]);
}
printf("\n");
}
}
int main()
{
memset(date,0,sizeof(date));
DP();
printf("%d\n",date[N][M]);
return 0;
}
可改进为
for (i=1;i<=N;i++)
for (j=w[i];j<=M;j++)
date[j]=max(date[j],date[j-w[i]]+v[i]);
2.最大连续子序列之和
给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序中元素和最大的一个, 例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20。
状态转移方程: sum[i]=max(sum[i-1]+a[i],a[i])
#include "stdio.h"
int main(){
int i,sum = 0, max = 0;
int data[] = {
1,-2,3,-1,7
};
for(i = 0; i < sizeof(data)/sizeof(data[0]); i++){
sum += data[i];
if(sum > max)
max = sum;
if(sum < 0)
sum = 0;
}
printf("%d",max);
}
3.数塔问题
数塔问题 :要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
转移方程:sum[i] = max(a[左孩子] , a[右孩子]) + a[i]
#include "stdio.h"
#define N 5
int main(){
int i,j;
int data[N][N] = {
{9,0,0,0,0},
{12,15,0,0,0},
{10,6,8,0,0},
{2,18,9,5,0},
{19,7,10,4,16}
};
for(i = N-1; i > 0; i--)
for(j = 0; j < i; j++)
data[i-1][j] += data[i][j] > data[i][j+1] ? data[i][j] : data[i][j+1];
printf("%d",data[0][0]);
}
4.最长递增子序列(LIS)
给定一个序列 An = a1 ,a2 , ... , an ,找出最长的子序列使得对所有 i < j ,ai < aj 。
转移方程:b[k]=max(max(b[j]|a[j]<a[k],j<k)+1,1);
#include "stdio.h"
int main(){
int i,j,length,max=0;
int a[] = {
1,-1,2,-3,4,-5,6,-7
};
int *b;
b = (int *)malloc(sizeof(a));
length = sizeof(a)/sizeof(a[0]);
for(i = 0; i < length; i++){
b[i] = 1;
for(j = 0; j < i; j++){
if(a[i] > a[j] && b[i] <= b[j]){
b[i] = b[j] + 1;
}
}
}
for(i = 0; i < length; i++)
if(b[i] > max)
max = b[i];
printf("%d",max);
}
5.最长公共子序列(LCS)
一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。
转移方程:
dp[i,j] = 0 i=0 || j=0
dp[i,j] = dp[i-1][j-1]+1 i>0,j>0, a[i] = b[j]
dp[i,j] = max(dp[i-1][j],dp[i][j-1]) i>0,j>0, a[i] != b[j]
#include "stdio.h"
#define M 8
#define N 6
void printLSC(int i, int j,char *a, int status[][N]){
if(i == 0 || j== 0)
return;
if(status[i][j] == 0){
printLSC(i-1,j-1,a,status);
printf("%c",a[i]);
}else{
if(status[i][j] == 1)
printLSC(i-1,j,a,status);
else
printLSC(i,j-1,a,status);
}
}
int main(){
int i,j;
char a[] = {' ','A','B','C','B','D','A','B'};
char b[] = {' ','B','D','C','B','A'};
int status[M][N]; //保存状态
int dp[M][N];
for(i = 0; i < M; i++)
for(j = 0; j < N; j++){
dp[i][j] = 0;
status[i][j] = 0;
}
for(i = 1; i < M; i++)
for(j = 1; j < N; j++){
if(a[i] == b[j]){
dp[i][j] = dp[i-1][j-1] + 1;
status[i][j] = 0;
}
else if(dp[i][j-1] >= dp[i-1][j]){
dp[i][j] = dp[i][j-1];
status[i][j] = 2;
}
else{
dp[i][j] = dp[i-1][j];
status[i][j] = 1;
}
}
printf("最大长度:%d",dp[M-1][N-1]);
printf("\n");
printLSC(M-1,N-1,a,status);
printf("\n");
}
6.找零钱问题
假设现在有1元、2元、5元的纸币很多张,现在需要20块钱,你能给多少种找钱方案,这就可以认为是完全背包问题,即背包容量为20,物品体积分别为1、2、5。
还有公司出题目:给定一个数m,将m拆成不同的自然数的和的形式有多少种方案,这就是典型的01背包问题,背包容量为m,物品件数为k,这里面的k是隐含条件,可以求出来,因为m最多由1+2+…+k得到,由此可以根据m求得物品件数的上限。
#include <iostream>
using namespace std;
const int M=1000;
const int N = 3;
int coint[N];
int count[M+1];//count[i]表示凑合数量为i所需最少的钱币数量,则count[i]=min{count[i-coint[j]]+1},其中0<=j<=N-1
int trace[M+1];//每个表示count[i]在取最小值时的选择,即上式中的j
int dp_count(int m)
{
int i = 0;
int j = 0;
for(i=0;i<M+1;i++)
count[i]=0xffff;
count[0] = 0;
for(i=0;i<=m;i++)
{
for(j=0;j<N;j++)
if(coint[j]<= i && count[i-coint[j]]+1 < count[i])
{
count[i] = count[i-coint[j]]+1;
trace[i] = coint[j];
}
}
return count[m];
}
void print(int m)
{
if(m==0)
return;
else
{
cout << trace[m] << " ";
print(m-trace[m]);
}
}
int main()
{
int i=0;
for(i=0;i<N;i++)
cin>>coint[i];
int m;
cin >> m;
cout<<dp_count(m)<<endl;
print(m);
}