01背包 (dp专题)
01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况
https://vjudge.net/contest/216347#problem/B
有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
已经告诉你了,这是个DP的题目,你能AC吗?
Output对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input
1 5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5Sample Output
30
#include<iostream> #include<stdio.h> #include<string.h> #include<cmath> #include<math.h> #include<algorithm> typedef long long ll; using namespace std; int main() { int t; int a[105][105],dp[105][105]; scanf("%d",&t); while(t--) { memset(dp,0,sizeof(dp)); int n; scanf("%d",&n); for(int i=0;i<n;i++) { for(int j=0;j<=i;j++) { scanf("%d",&a[i][j]); } } for(int i=n-1;i>=0;i--) { for(int j=0;j<=i;j++) dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]); } printf("%d\n",dp[0][0]); } return 0; }
题目大意:有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值
输入:n,k; n代表n个物品,k代表能挑选的最大最大重量(1<=n<=100,1<=wi,vi<=100,1<=w<=10000)
下面2*n个数分别代表每个物品的w[i],v[i]
输出:最大价值
#include<iostream> #include<stdio.h> #include<string.h> #include<cmath> #include<math.h> #include<algorithm> #include<set> typedef long long ll; using namespace std; int dp[150][150]; int w[150],v[150];//下面的两种写法都可以 int n,k; /* int solve(int a,int b)//a代表当前的物品,b代表还能装多少重量 { if(dp[a][b]>=0) return dp[a][b]; int rec; if(a==n)//已经没有物品了 rec=0; else if(b<w[a])//无法挑选这个物品 rec=solve(a+1,b); else rec=max(solve(a+1,b),solve(a+1,b-w[a])+v[a]); return dp[a][b]=rec; } */ int main() { memset(dp,0,sizeof(dp)); scanf("%d%d",&n,&k); for(int i=0;i<n;i++) scanf("%d%d",&w[i],&v[i]); for(int i=n-1;i>=0;i--) { for(int j=0;j<=k;j++) { if(j<w[i]) dp[i][j]=dp[i+1][j];//dp[i][j]为从第i个物品开始挑选总重小于j时,总价值的最大值 else dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]); } } printf("%d\n",dp[0][k]); return 0; }
题目大意:
有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值
输入:n,k; n代表n个物品,k代表能挑选的最大最大重量(1<=n<=100,1<=wi<=10^7,1<=vi<=100,1<=w<=10^9)
下面2*n个数分别代表每个物品的w[i],v[i]
输出:最大价值
这题与上一题相比较,就只有限制条件不一样,但是如果用上题的方法,复杂度n*w,就显然太大了,试着思考,相比较于重量而言,价值的范围比较小,所以可以试着
改变dp的对象。之前的方法中,我们用dp针对不同的重量限制计算最大的价值。这次不妨用不同的价值针对不同的重量
定义dp[i+1][j]:=前i个物品中挑选出价值总和为j时重量最小值(不存在就是一个充分大的数值INF)。由于前0个物品什么都挑选不了,所以初始化为
dp[0][0]=0
dp[0][j]=INF
此外,前i个物品中挑选出价值总和为j时,一定有
前i-1个物品中挑选出价值总和为j的部分
前i-1个物品中挑选价值总和为j-v[i]的部分,然后再选中第i个物品
所以能够得到:dp[i+1][j]=min(dp[i][j],dp[i][j-v[i]]+w[i])
利用这一递推式,最终答案就是令dp[n][j]<=W的最大j
当然如果价值太大的话这一方法也行不通,也就会有像这种需要根据问题的规模来改变算法的情况存在
#include<iostream> #include<stdio.h> #include<string.h> #include<cmath> #include<math.h> #include<algorithm> #include<set> typedef long long ll; using namespace std; #define INF 1e9+7 #define MAX_N 100 #define MAX_V 100 int n,m; int dp[MAX_N+1][MAX_N*MAX_V+1]; int w[1100],v[1100]; int main() { int n,m; scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d%d",&w[i],&v[i]); scanf("%d",&m); fill(dp[0],dp[0]+MAX_N*MAX_V+1,INF); dp[0][0]=0; for(int i=0;i<n;i++) { for(int j=0;j<=MAX_N*MAX_V+1;j++) { if(j<v[i]) dp[i+1][j]=dp[i][j]; else dp[i+1][j]=min(dp[i][j],dp[i][j-v[i]]+w[i]); } } int res=0; for(int i=0;i<=n*m;i++) if(dp[n][i]<=m) res=i; printf("%d\n",res); return 0; }
题目:最长公共子序列
题目大意:给定两个子序列,s1s2...sn和t1t2...tn。求出这两个字符串最长的公共子序列的长度。
字符串s1s2...sn的子序列可以不表示为si1si2...sin(i1<i2<in) (就是可以不连续的意思)
限制条件:1<=n,m<=1000
样例:
输入 n=4 m=4 s="abcd" t="becd"
输出 3("bcd")
dp[i][j] i,j代表s和t的长度,dp[i][j]最长公共子序列长度
当si+1==tj+1时
dp[i+1][j+1]=max(dp[i][j]+1,dp[i][j+1],dp[i+1][j])
si+1!=tj+1时
dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1])
#include<iostream> #include<stdio.h> #include<string.h> #include<cmath> #include<math.h> #include<algorithm> #include<set> typedef long long ll; using namespace std; int n,m;//代表两个串的长度 char s[1100],t[1100]; int dp[1100][1100]; int main() { memset(dp,0,sizeof(dp)); scanf("%d%d",&n,&m); getchar(); scanf("%s%s",s,t); for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { if(s[i]==t[j]) dp[i+1][j+1]=dp[i][j]+1; else dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]); } } printf("%d\n",dp[n][m]); return 0; }
题目链接:https://vjudge.net/contest/216347#problem/A
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。
n=0表示数据结束。
Output对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。Sample Input
1 50 5 10 1 2 3 2 1 1 2 3 2 1 50 0Sample Output
-45 32
典型的01背包,每种菜只有买或者不买的情况,但是要注意限制条件,可以用5元买任意价格的菜,所以为了使利益最大化,我们就让5元去买最贵的,这样就行了
#include<iostream> #include<stdio.h> #include<string.h> #include<cmath> #include<math.h> #include<algorithm> #include<set> typedef long long ll; using namespace std; int n,m; int dp[1100][1100]; int a[1100]; int main() { while(scanf("%d",&n)!=EOF) { memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++) { scanf("%d",&a[i]); } if(n==0) break; scanf("%d",&m); if(m<5) printf("%d\n",m); else { sort(a,a+n); for(int i=n-2;i>=0;i--) { for(int j=0;j<=m-5;j++) { if(j<a[i]) dp[i][j]=dp[i+1][j]; else dp[i][j]=max(dp[i+1][j],dp[i+1][j-a[i]]+a[i]); } } printf("%d\n",m-dp[0][m-5]-a[n-1]); } /* for(int i=0;i<n-1;i++) { for(int j=0;j<m;j++) printf("%d ",dp[i][j]); printf("\n"); } */ } return 0; }
题目链接:https://vjudge.net/contest/236677#problem/M
George met AbdelKader in the corridor of the CS department busy trying to fix a group of incorrect equations. Seeing how fast he is, George decided to challenge AbdelKader with a very large incorrect equation. AbdelKader happily accepted the challenge!
InputThe first line of input contains an integer N (2 ≤ N ≤ 300), the number of terms in the equation.
The second line contains N integers separated by a plus + or a minus -, each value is between 1 and 300.
Values and operators are separated by a single space.
OutputIf it is impossible to make the equation correct by replacing operators, print - 1, otherwise print the minimum number of needed changes.
Examples7
1 + 1 - 4 - 4 - 4 - 2 - 2
3
3
5 + 3 - 7
-1
题目大意:输入n,代表有n个数,数与数之家有用空格分隔的'+'或者'-',你可以把'+'改为'-',也可以把'-'改为'+',问你最少改多少次使得式子等于0
个人思路:自己没做出来,借鉴他人的··· 把这道题当作01背包来做,dp[i][j]代表从第1~i个数中使得重量为j的最少操作次数。
状态转移方程:
if(j>=a[i])
dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]) 代表不改变符号
if(j>=-a[i])
dp[i][j]=min(dp[i][j],dp[i-1][j+a[i]]+1) 代表改变符号,操作数+1
#include<iostream> #include<stdio.h> #include<string.h> #include<cmath> #include<math.h> #include<algorithm> #include<set> typedef long long ll; using namespace std; const ll mod=1e9+7; #define INF 0x3f3f3f int a[310]; int dp[310][90000*2+100];//dp[i][j]表示第1~i个数,使剩余答案为j时的操作数 int main() { int n,sum=0; char b; scanf("%d",&n); scanf("%d",&a[1]); sum=a[1]; for(int i=2;i<=n;i++) { scanf(" %c %d",&b,&a[i]); sum+=a[i]; if(b=='-') a[i]=-a[i];//用a[i]存入每个数本身的值 } //fill(dp,dp+10,INF); for(int i=1;i<=n;i++) { for(int j=0;j<=sum*2;j++) dp[i][j]=INF; } if(sum%2==1) printf("-1\n"); else { dp[1][sum/2+a[1]]=0;//用sum/2来当作起点 for(int i=2;i<=n;i++) { for(int j=0;j<=sum*2;j++) { if(j>=a[i]) dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]);//不改变符号 if(j>=-a[i]) dp[i][j]=min(dp[i][j],dp[i-1][j+a[i]]+1);//改变符号 } } printf("%d\n",dp[n][sum/2]); } return 0; }