01背包 (dp专题)

01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况

 

https://vjudge.net/contest/216347#problem/B

在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:

有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

已经告诉你了,这是个DP的题目,你能AC吗?
Input输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
Output对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input
1
5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5
Sample 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

电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
Input多组数据。对于每组数据:
第一行为正整数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
0
Sample 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!

Input

The 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.

Output

If it is impossible to make the equation correct by replacing operators, print  - 1, otherwise print the minimum number of needed changes.

Examples
Input
7
1 + 1 - 4 - 4 - 4 - 2 - 2
Output
3
Input
3
5 + 3 - 7
Output
-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;
}

  

posted @ 2018-07-05 11:26  执||念  阅读(215)  评论(0编辑  收藏  举报