zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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("
    ");
            }
            */
    
    
        }
        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
    ");
    
        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
    ",dp[n][sum/2]);
        }
        return 0;
    }
    

      

    当初的梦想实现了吗,事到如今只好放弃吗~
  • 相关阅读:
    centos8.2安装Rabbitmq-3.8.12
    绝对好文C#调用C++DLL传递结构体数组的终极解决方案
    c++到c#数据类型的转换
    WPF中timer的使用
    接收Dialog的值
    WPF子界面向父界面传递带参数的委托
    2015上海居住证和积分办理流程
    Discuz! X3.2重置管理员账号
    winServer2008下安装SqlServer2008数据库
    jodd cache实现缓存超时
  • 原文地址:https://www.cnblogs.com/caijiaming/p/9267412.html
Copyright © 2011-2022 走看看