背包专题

一、 01背包

1.题目

1) HDU 4526 

地址:http://acm.hdu.edu.cn/showproblem.php?pid=4526

题意:N个人要在S分钟内乘K俩车到达比赛现场,每辆车Ki分钟到达,剩余Zi个座位,不管几个人上一辆车,费用都是D,对每一辆车可选择做还是不做,不做产生等待费用,等待费用是时间乘以人数,问最后在所有人能到达比赛现场的情况下,话费最少的钱是多少?

解法:对第i俩车,可选择做还是不做,dp[i][j] 表示前i辆车做了j人所需要的最小费用,典型的01背包问题。

dp方程:

dp[i][j] = min(dp[i-1][j] + (ptime[i] - ptime[i-1]) * (N-j) , dp[i-1][j - k] +( N - j + k) * (ptime[i] - ptime[i-1]) + D);

令我羞愧的是,比赛时我设的i从0开始,初始dp[0][0] = ptime[0] * N ,其他dp元素的值为inf, 一直WA~~~ 

将dp[0][0] = 0,就AC了~~~不解~~以后还是都从1开始吧~~惨痛的教训,认真分析方程含义,处理各种边界情况,特别是初始值问题

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
const int inf = 0x7f7f7f7f;
#define MAXN 110
int ptime[110], seat[110], dp[110][510];
/*

memeset(dp, 0x7f, sizeof(dp));
dp[i][0] = N * time[i]
dp[i][v], 表示到第i辆车时,累计已经有v个人上车了

for(int i = 1; i <= seat[0]; i++)
  dp[0][i] = D + ( N - i ) * time[1];

dp[i][v] = min(dp[i-1][v - seat[i]] + (N - v + seat[i] )* (time[i] - time[i-1]) + D, dp[i-1][v] + (time[i] - time[i-1]) * (N-v) )

*/


int main( )
{
  int N,K,D,S,T;
  scanf("%d",&T);
  while(T--)
  {
     scanf("%d%d%d%d",&N,&K,&D,&S);
     memset(ptime, 0, sizeof(ptime));
     memset(seat, 0, sizeof(seat));
     memset(dp, 0x7f, sizeof(dp));
     dp[0][0] = 0;
     for(int i = 1; i <= K; ++i)
     {
        scanf("%d%d",&ptime[i], &seat[i]);
     }
     for(int i = 1; i <= K; ++i)
        dp[i][0] = ptime[i] * N;
     for(int i = 1; i <= K; ++i)
     {
        for(int j = 0; j<= N; ++j)
        {
           dp[i][j] = dp[i-1][j] + (ptime[i] - ptime[i-1]) * (N - j);
           for(int k = 0; k <= seat[i]; ++k)
           {
                if( j - k >= 0 )
                {
                  dp[i][j] = min(dp[i-1][j - k] +( N - j + k) * (ptime[i] - ptime[i-1]) + D,dp[i][j]);
                }
                
           }
        }     

     }
     if( dp[K][N] == inf )
       puts("impossible");
     else
       printf("%d\n",dp[K][N]);
     
  }
  return 0;
}

 另外一种简单写法,此外还可以优化成一维dp

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

const int inf = 0x3f3f3f3f;

int main( )
{
  int T, N, K, D, S;
  scanf("%d",&T);
  int ptime[110],seat[110], dp[110][110];
  while(T--)
  {
    scanf("%d%d%d%d",&N,&K,&D,&S);
    for(int i = 1; i <= K; ++i)
       scanf("%d%d",&ptime[i],&seat[i]);
    memset(dp,0x3f,sizeof(dp));
    dp[0][0] = 0;
    for(int i = 1; i <= K; i++)
    {
      for(int j = 0; j <= N; j++)
      {
         dp[i][j] = dp[i-1][j];
         for(int k = 1; k <= seat[i]; ++k)
         {
           if( j - k >= 0 )
           dp[i][j] = min(dp[i-1][j-k] + D + k * ptime[i], dp[i][j]);
         }        
      }
            
    }
    if( dp[K][N] == inf )
        puts("impossible");
    else
        printf("%d\n",dp[K][N]);
              
      
  }
  return 0;    
}

2. coins

题意:小明有N种不同分值的硬币,每个分值的硬币数量为Ci,手表费用不超过M, 问小明的这些硬币能组成多少种钱(1-M)。

算法:

1.动态规划解法

对于每一种分值的硬币,可选择取于不取,还可以选择取的数量,用二进制将数量转为一个单一物品,比如16 = 1 + 2 + 4 + 8 + 1,

数量16的物品,可以由五个物品组成,总的价钱不能超过M。

1 ≤ N ≤ 100  M≤ 100000

dp[M]表示硬币组成钱的和为M时,是否可以组成M钱。

最后从1遍历到M,统计能组成的钱数。

最坏时间复杂度为O(N * M)

代码:

View Code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int v[110],num[110];
int dp[100010];
int value[2000];

int max(int x, int y)
{
  return x > y ? x : y;
}
int main( )
{
  int n,m;
  while( scanf("%d%d",&n,&m), n||m )
  {
     for(int i = 1; i <= n; ++i)
    scanf("%d",&v[i]);
     for(int i = 1; i <= n; ++i)
    scanf("%d",&num[i]);
     memset(dp, 0, sizeof(dp));
     //将num[i]插为二进制表示
     //16 = 1 2 4 8 1
     int cnt = 0;
     for(int i = 1; i <= n; ++i)
     {
         int t = 1, left = num[i],flag = 0;
         while( left >= t )
         {
            //printf("t = %d\n",t);
            if( t * v[i] > m ) { flag = 1; break; }
            value[++cnt] = t * v[i];
            left -= t;
            t <<= 1;
           
         }
         //printf("left = %d\n",left);
         if( left && !flag ) value[++cnt] = left * v[i];

     }
     //对于value中每个物品,可以选一个,也可以选多个
     //dp[v] = dp[v - value[i] ]
     dp[0] = 1;
     for(int i = 1; i <= cnt; i++)
     {
        for(int V = m; V >= value[i]; --V)
        {
           if( V >= value[i] && dp[V-value[i]] != 0 )
             dp[V] = dp[V - value[i] ];
           //else
             //dp[V] = dp[V-1];
        }

     }
     cnt = 0;
     for(int i = 1; i <= m; i++)
        if( dp[i] ) ++cnt;
     printf("%d\n",cnt);
     
  }
  return 0;
}

母函数解法:

(1+x1 + x1^2 + x1^3 + ..) ....(1 + xn + x^2n + x^3n)

 xn表示x的n次方。

统计最后x的次数小于等于M,且系数不为0的个数,就是能组成的钱数。

最坏时间复杂度为O( N * M * MAX(ci) ) 果断TLE

代码:

View Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXN 102000
int c1[MAXN], c2[MAXN];
int v[MAXN], num[MAXN], sum[MAXN];

/*
(1+x1 + x1^2 + x1^3 + ..) ....(1 + xn + x^2n + x^3n)

*/

int solve(int n, int m)
{
  //memset(sum, 0, sizeof(sum));
  //for(int i = 1; i <= n; i++)
    // sum[i] += sum[i-1] + v[i] * num[i]; //统计前i项时x最高次数

  for(int i = 0; i <= m; i++)
  {
      c1[i] = 0;
      c2[i] = 1; //每个括号中系数的值
  }
  //c1[0] = 1; 
  int maxn = 0, temp = 0;
  for(int i = 1; i <= n; ++i) //n个括号,n 轮运算
  {
     temp = maxn;
     for(int j = 0; j <= temp && j <= m; j++) //枚举前i-1个括号所得值
     {
          //int ans = num[i] * v[i];
          for(int k = 0, l = 0; l <= num[i] && (j + k) <= m; k += v[i], l++)
          {
              c1[ j + k ] += c2[k];
              maxn = (j + k) > maxn ? (j + k) : maxn;

          }
          //c2[ j ] += c1[j];

     }

  }
  int cnt = 0;
  for(int i = 1; i <= m; i++)
    if( c1[i] )
       ++cnt;
   return cnt;
}

int main( )
{
   int n,m;
   while( scanf("%d%d",&n,&m), n||m )
   {
      for(int i = 1; i <= n; ++i)
    scanf("%d",&v[i]);
      for(int i = 1; i <= n; ++i)
    scanf("%d",&num[i]);
      printf("%d\n",solve(n,m));                  
   }      
   return 0;
}

 

 

posted on 2013-03-26 14:45  luckyboy1991  阅读(157)  评论(0编辑  收藏  举报