DP~背包

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2602

题意:    有N 个骨头,你有一个体积为M  的背包。     给你每个骨头的价值va[i], 和体积vo[i]  

思路:直接引网上原话(感觉很好理解)       

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。              

  用子问题定义状态:即dp[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:dp[i][v]=max{ dp[i-1][v], dp[i-1][v-vo[i]]+va[i] }。可以压缩空间,dp[v]=max{dp[v], dp[v-vo[i]]+va[i]}      

  dp[i][v] 指 “将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为dp[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-vo[i]的背包中”,此时能获得的最大价值就是dp[i-1][v-vo[i]]再加上通过放入第i件物品获得的价值va[i]。

 代码:

//f[i]  = max(f[j] , f[j- vo[i]]+va[i]);
  
     #include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define max(a, b) (a>b?a:b)
#define min(a, b) (a<b?a:b)

int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int i, n, m;                     // n代表物品总个数  m 代表背包总体积
        int f[1002], va[1002], vo[1002]; //f[i]代表背包体积为 i 时可以装的东西的最大价值
        scanf("%d%d", &n, &m);
        for(i=0; i<n; i++)               //va数组存储每个物品的价值
            scanf("%d", &va[i]);
        for(i=0; i<n; i++)
            scanf("%d", &vo[i]);         // vo数组存储每个物品的体积
        int j, temp;
        memset(f, 0, sizeof(f));        //初始化f数组
        for(i=0; i<n; i++)
            for(j=m; j>=vo[i]; j--)      //f[i]  = max(f[j] , f[j- vo[i]]+va[i]);
                f[j] = max(f[j], f[j-vo[i]]+va[i]);
        printf("%d\n", f[m]);
    }
    return 0;
}
// dp[i][j] = max(dp[i-1][j], dp[i-1][j-vo[i]]+va[i])
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define max(a, b) (a>b?a:b)
#define min(a, b) (a<b?a:b)

int va[1002], vo[1002], dp[1002][1002]; //二维数组太大时要开在main函数外面
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int n, m;
        scanf("%d%d", &n, &m);
        int i, j;
        for(i=1; i<=n; i++)
            scanf("%d", &va[i]);
        for(j=1; j<=n; j++)
            scanf("%d", &vo[j]);
        memset(dp, 0, sizeof(dp));  //初始化dp数组
        for(i=1; i<=n; i++)         //i对应第几个物品
            for(j=0; j<=m; j++)     //j对应体积
            {
                dp[i][j] = dp[i-1][j];  //放第i件物品时,背包体积V小于第i件物品的体积,则没有放第i件物品其最大价值为其                                                         //原值
                if(j >= vo[i])            //背包体积足够时  (背包体积v大于第i 件物品的体积)
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-vo[i]] + va[i]);
            }
        printf("%d\n", dp[n][m]);
    }
    return 0;
}

 

完全背包:  若上一题中每件物品只有一件改成每件物品有任意多件。就成完全背包问题。。

 思路:dp[i][j] 表示前i件物品在到体积为j的背包中的最大价值

  最直接的思路就是 :     

   》dp[i+1][j] =  max{ dp[i-1][j-k*v[i+1]] + k*v[i+1]  | (k>=0) } k代表第i+1件物品取多少件。 然而这样就有三个for循环 算法复杂度很大~~  

  上面的公式可以简化:   

    》dp[i+1][j] = max{ dp[i][j-k*w[i+1]] + k*v[i+1]  | (k>=0) }                        

           = max{ dp[i][j], max{ dp[i+1][j-k*w[i+1]]+k*v[i+1] | k>=1}         ........B                

           = max{ dp[i][j], max{ dp[i+1][j-w[i+1]-k*w[i+1] ]+k*v[i+1] | 0<=k } +v[i+1] } .......C               

           = max{ dp[i][j], dp[i+1][j-w[i+1]]+v[i+1] }            .......D

    B  当k取0时,当前的最大值就是 max{第i+1件不取, 第i+1件取(k>=1)件 } 

    C   第i+1件取(k>=1)件,前面已经算出第i+1件取(k-1)件时的最大值。(在计算dp[i+1][j-w[i]]中, 选择第i+1件物品的最大值都是 dp[i+1][j-k*w[i+1]]+k*v[i+1], 所以前面计算过可以少一些重复计算~~)

    D   所以化简就行。。 最后的公式就是 : dp[i+1][j] = max{ dp[i][j], dp[i+1][j-w[i+1]]+v[i+1] }

 

问题:n个无区别物品,划分成m组,划分方法对mod取余 dp[i][j] 表示j划分为i组 n划分为m组,每组为ai,如果所有ai>0,则所有的ai都 -1,代表n-m的m划分,此外,如果存在ai=0,则对应了n的m-1次划分。

//dp[i][j] = dp[i-1][j]+dp[i][j-i];
int n, m, mod;
void solve()
{
    dp[0][0]=1;
    for(int i=1; i<=m; i++)
        for(int j=0; j<=n; j++)
        {
            if(j>=i) dp[i][j]=dp[i-1][j]+dp[i][j-i]; 
            else  dp[i][j]=dp[i-1][j];
            dp[i][j] %= mod;
        }
}

问题:n种物品,每种无区别,第i种有ai个,从中取出m个,取法对于mod取余
dp[i][j] 表示前i种物品共取j件, 则
dp[i+1][j] = { dp[i][j-k] | (0<=k<=min(j, a[i-1]) };
             = dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a[i]]
第一项 前i件物品取j-1件;
第二项 第i件物品取0件,前i-1件物品取j件;
第三项 第i-1件物品取0件,前i-2件物品取j-1-a[i-1]件

 

int n, m, mod;
void solve()
{
    for(int i=0; i<=n; i++) //一个都不取方法只有1
        dp[i][0]=1;
    for(int i=0; i<n; i++)
        for(int j=1; j<=m; j++)
        {
            if(j-1>=a[i])
                dp[i+1][j] = (dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a[i]]+mod)%mod;
            else  dp[i+1][j] = (dp[i+1][j-1]+dp[i][j])%mod;
        }
}

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1176

思路:      dp[i][j]表示在第i秒内在j位置时收到馅饼数量的最大值。( dp题目这里就是关键,想好状态怎么表示。)     

     map[i][j]表示在第i秒内在j位置时的馅饼数。

状态方程:   dp[i][j]= max(dp[i+1][j-1], dp[i+1][j], dp[i+1][j+1]) + map[i][j];

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define max(a, b) (a>b?a:b)
#define maxx(a, b, c) (max(a, (max(b, c))))
int dp[100002][12];//因为map数组每一个点只用一次,so 用dp数组表示map数组和dp数组 
int n; 
int main()
{
    while(scanf("%d", &n) && n)
    {
        int i, x, t, maxt=0;
        memset(dp, 0, sizeof(dp));
        for(i=1; i<=n; i++)
        {
            scanf("%d%d", &x, &t);
            dp[t][x+1] += 1;   //这样对于0就不用的特殊处理
            if(t>maxt) maxt=t;
        }
        for(i=maxt-1; i>0; i--) //从最底层
            for(t=1; t<=11; t++) //t=0时不用特殊处理就是从1开始优点
                   dp[i][t] += maxx(dp[i+1][t-1], dp[i+1][t], dp[i+1][t+1]);
        printf("%d\n", maxx(dp[1][5], dp[1][6], dp[1][7]));
    }
    return 0;
}

 

posted @ 2015-09-21 22:14  马晨  阅读(92)  评论(0)    收藏  举报