ACM Steps_Chapter Three_Section3

Bone Collector

/*
标准的01背包问题。状态转移方程

f[i][v] = max{f[i-1][v-c[i]]+v[i],f[i-1][v]}
*/
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
int main()
{
	int T,N,V,f[1001],vol[1001],val[1001],tem;
	cin>>T;
	while(T--)
	{
		cin>>N>>V;
		for(int i=0;i<N;i++)
		{
			cin>>val[i];
		}
		for(int i=0;i<N;i++)
		{
			cin>>vol[i];
		}
		memset(f,0,sizeof(f));
		for(int i=0;i<N;i++)
		{
			for(int j=V;j>=vol[i];j--)
			{
				tem=f[j-vol[i]]+val[i];
				if(f[j]<tem)
					f[j]=tem;
			}
		}
		cout<<f[V]<<endl;
	}
	system("pause");
	return 0;
}

Coins

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[100010],w[110],c[110];
int main()
{
    int n,m,i,j,k,cnt;
    while( scanf("%d%d",&n,&m),n+m )
    {
        cnt=0;
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++) 
			scanf("%d",&w[i]);
        for(i=1;i<=n;i++) 
			scanf("%d",&c[i]);
        for(i=1;i<=n;i++)
        {
            if( c[i]*w[i] > m )  // 完全背包
            {
                for( j=w[i];j<=m;j++ )
                    dp[j]= max( dp[j],dp[j-w[i]]+w[i] );
            }
             	//多重背包 转化为 01背包 ,
				//把物品的个数 c[i] 拆分为2^0,2^1,……,2^k,c[i]-2^k+1; 
				//1至c[i]中的数肯定能够分解为前面的数的和。就是十进制转二进制
			else
            {
                k=1;               
                int num=c[i];
                while( k < num )
                {
                    for( j=m; j>=k*w[i] ; j--)
                        dp[j]=max( dp[j],dp[j-k*w[i]]+k*w[i]);  //拆分是对c[i]来说的,
						//ij具体操作时其实是把2^k个物品组合为一个物品来操作
                    num-=k;
                    k<<=1;
                }
                for( j=m;j>=num*w[i];j--)
                    dp[j]=max( dp[j],dp[j-num*w[i]]+num*w[i] );
            }
        }
        for(i=1;i<=m;i++) if( dp[i]==i ) cnt++;
        printf("%d\n",cnt);
    }
    return 0;
}

Ahui Writes Word

/*
题目大意:
 Ahui写作文,每个单词都有复杂度和value,先输入N,C,代表整篇作文有n个单词,
 然后作文的最大复杂度小于V。(1 ≤ N ≤ 100000, 1 ≤ C ≤ 10000),后面跟N行,
 每行输入单词,还有单词的价值val跟单词的复杂度compl
(0<=val<=com<=10),求在单词的复杂度不超过C的情况下,Ahui能够获得的最大价值。


解题思路:
一般的01背包会TLE,因为N * C = 1000000000;但是仔细考虑下,
其实每个单词v跟c都小于等于10,所以这么大的输入量,必定有单词的价值跟复杂度是完全相同的,
因为10*10=100;因此可以考虑用多重背包减小时间复杂度。


P03: 多重背包问题

题目:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],
价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,
且价值总和最大。

基本算法:这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,
因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。
令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,
则有状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}

复杂度是O(V*Σn[i])。

转化为01背包问题

另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,
则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σn[i])。

但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。
仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,
使得原问题中第i种物品可取的每种策略——取0..n[i]件——
均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。

方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,
这件物品的费用和价值均是原来的费用和价值乘以这个系数。
使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。
例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。

分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。
另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,
这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。

这样就将第i种物品分成了O(log n[i])种物品,
将原问题转化为了复杂度为<math>O(V*Σlog n[i])的01背包问题,是很大的改进。

下面给出O(log amount)时间处理一件多重背包中物品的过程,其中amount表示物品的数量:

procedure MultiplePack(cost,weight,amount)
    if cost*amount>=V
        CompletePack(cost,weight)
        return
    integer k=1
    while k<amount
        ZeroOnePack(k*cost,k*weight)
        amount=amount-k
        k=k*2
    ZeroOnePack(amount*cost,amount*weight)

希望你仔细体会这个伪代码,如果不太理解的话,不妨翻译成程序代码以后,
单步执行几次,或者头脑加纸笔模拟一下,也许就会慢慢理解了。


O(VN)的算法

多重背包问题同样有O(VN)的算法。这个算法基于基本算法的状态转移方程,
但应用单调队列的方法使每个状态的值可以以均摊O(1)的时间求解。
由于用单调队列优化的DP已超出了NOIP的范围,故本文不再展开讲解。
我最初了解到这个方法是在楼天成的“男人八题”幻灯片上。

小结
这里我们看到了将一个算法的复杂度由O(V*Σn[i])改进到O(V*Σlog n[i])的过程,
还知道了存在应用超出NOIP范围的知识的O(VN)算法。希望你特别注意“拆分物品”的思想和方法,
自己证明一下它的正确性,并将完整的程序代码写出来。
*/
#include<stdio.h>
 #include<string.h> 
 char ch[100] ;
 int nkind , total_comp ;
 int value[100024] , comp[100024] , fine[100024] , map[11][11];
 int main ()
 {
     int pos , x , y ,sum , t ;
     while ( scanf ( "%d%d" , &nkind , &total_comp ) != EOF )
     {
           memset( map , 0 , sizeof (map) ) ;
           for ( int i = 0 ; i < nkind ; i ++ )
           {
               scanf ( "%s%d%d" , ch , &x , &y ) ;
               map[x][y] ++ ;
           }
           pos = 0 ;                           // 用二分制转换成01背包 
           for ( int i = 0 ; i <= 10 ; i ++ )
               for ( int j = 0 ; j <= 10 ; j ++ ) 
               {
                   if( map[i][j] >= 1  )
                   { 
                       sum = 1 , t = 1 ;
                       while ( sum <= map[i][j] )
                       {
                             value[pos] = i * t ;
                             comp[pos++] = j * t ;
                             t *= 2 ;
                             sum += t ;
                       }
                       if ( ( sum - t ) < map[i][j] )
                       {
                            value[pos] = i * (map[i][j]-sum + t) ;
                            comp[pos++] = j * (map[i][j]-sum + t) ;
                       }
                   }
               }
           memset( fine , 0 , sizeof (fine) ) ;
           for ( int i = 0 ; i < pos ; i ++ )
               for ( int j = total_comp ; j >= comp[i] ;  -- j )
                   if ( fine[j] < fine[j-comp[i]] + value[i] )
                        fine[j] = fine[j-comp[i]] + value[i] ; 
           printf ( "%d\n" , fine[total_comp] ) ;
     }
     return 0 ;
 }

Robberies

/*
状态转移方程是:dp[j]=max(dp[j],dp[j-m[i]]*(1 - q[i]))  
其中,dp[j]表示抢j块大洋的最大的逃脱概率,条件是dp[j-m[i]]可达,也就是之前抢劫过;
始化为:dp[0]=1,其余初始化为-1.
*/
 
#include<iostream>
using namespace std;
double max(double a,double b)
{
    return a>b?a:b;
}
int main()
{
    int t;
    cin>>t;
    for(int x =1; x <= t; x++)
    {
        double p;
        cin>>p;
        int n;
        cin>>n;
        int i,j;
        int* m = new int[n+1];
        double * q = new double[n+1];
        int  s = 0;
        for(i = 1; i <= n; i++)
        {
            cin>>m[i]>>q[i];
            s += m[i];
        }
        double *dp = new double[s+1];
        dp[0] = 1;
        for(i = 1; i <= n; i++)
        for(j = s; j >= m[i]; j--)
        {
            dp[j] = max(dp[j],dp[j - m[i]]*(1-q[i]));
        }
        for(i = s; i >= 0; i--)
        if(dp[i] >= 1-p)
        {
            cout<<i<<endl;
            break;
        }
    }
}

Watch The Movie

#include<cstdio>
#include<cstring>
#define max(a,b) a>b?a:b
#define inf 0x7fffffff 
int N,M,L,t[105],v[105],dp[105][1005];
int main()
{
    int T,i,j,k;
    scanf("%d",&T);
    while(T--)
    {
              //想买N部(即物品数量),最多卖M部(背包容量),最大时限为L
              //每部耗时t[i],价值v[i]
              //求在不超过最大时限L,且看完所有M部电影 的情况下获得的最大价值 
              //第一层背包:时间背包; 第二层背包:数量背包 
              scanf("%d%d%d",&N,&M,&L);
              for(i=0;i<N;i++)
                scanf("%d%d",&t[i],&v[i]);        
              
              //初始化dp             
              for(i=0;i<=M;i++)
                for(j=0;j<=L;j++)
                {  if(i==0) dp[i][j]=0;
                   else dp[i][j]=-inf; 
                }
                
              //二维背包dp  
                                      
              for(j=0;j<N;j++)  //注意枚举N个物品的变量必须放在最外层 
                 for(k=L;k>=t[j];k--)
                     for(i=1;i<=M;i++)
                      dp[i][k]=max(dp[i][k],dp[i-1][k-t[j]]+v[j]);
               //判断     
              if(dp[M][L]>0)printf("%d\n",dp[M][L]);
              else  printf("0\n");             
    }
}

饭卡

/*
这是一个变形的01背包问题,首先如果金额小于5元,剩余金额不变,为已有金额。如果大于等于5元
我们先用5元买最贵的菜。然后用剩下的钱买其他的菜这时就是一个典型的01背包问题了;
求出最大的花费,然后用总金额减去最大的花费即为剩余金额。
抽象的地方是花费和价值都是用金额表示。
*/
#include <iostream>
 #include <stdio.h>
 #include <algorithm>
 #include <string.h>
 using namespace std;
 bool cmp(int a,int b)
 {
     return a<b;
 }
 int main()
 {
     int n,k[1001],f[1001],m;
     while(scanf("%d",&n),n)
     {
         for(int i = 0; i < n; i++)
         {
             scanf("%d",&k[i]);
         }
         sort(k,k+n,cmp);
         scanf("%d",&m);
         if(m>=5)
         {
             memset(f,0,sizeof(f));
             for(int i = 0; i < n-1; i++)
             for(int j = m-5; j>=k[i];j--)
             {
                 if(f[j-k[i]]+k[i]>f[j]&&f[j-k[i]]+k[i]<=m-5)//注意这里的f[j-k[i]]+k[i]<=m-5。
                 f[j] = f[j-k[i]]+k[i];
             }
             printf("%d\n",m-f[m-5]-k[n-1]);
         }
         else printf("%d\n",m);
     }
     return 0;
 }

Watch The Movie

#include<cstdio>
#include<cstring>
#define max(a,b) a>b?a:b
#define inf 0x7fffffff 
int N,M,L,t[105],v[105],dp[105][1005];
int main()
{
    int T,i,j,k;
    scanf("%d",&T);
    while(T--)
    {
              //想买N部(即物品数量),最多卖M部(背包容量),最大时限为L
              //每部耗时t[i],价值v[i]
              //求在不超过最大时限L,且看完所有M部电影 的情况下获得的最大价值 
              //第一层背包:时间背包; 第二层背包:数量背包 
              scanf("%d%d%d",&N,&M,&L);
              for(i=0;i<N;i++)
                scanf("%d%d",&t[i],&v[i]);        
              
              //初始化dp             
              for(i=0;i<=M;i++)
                for(j=0;j<=L;j++)
                {  if(i==0) dp[i][j]=0;
                   else dp[i][j]=-inf; 
                }
                
              //二维背包dp  
                                      
              for(j=0;j<N;j++)  //注意枚举N个物品的变量必须放在最外层 
                 for(k=L;k>=t[j];k--)
                     for(i=1;i<=M;i++)
                      dp[i][k]=max(dp[i][k],dp[i-1][k-t[j]]+v[j]);
               //判断     
              if(dp[M][L]>0)printf("%d\n",dp[M][L]);
              else  printf("0\n");             
    }
}

Proud Merchants

/*
按照q-p从小到大排序,然后01背包。
至于按照q-p从小到大排序比较难想到。q-p其实就是不更新的范围,
不更新的范围从小到大递增时就不会影响后面的DP了。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int MAXN=550;
struct Node
{
    int p,q,v;
}node[MAXN];
int dp[5500];
bool cmp(Node a,Node b)//按照 q-p 从小到大排序
{
    return a.q-a.p < b.q-b.p;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=0;i<n;i++)
          scanf("%d%d%d",&node[i].p,&node[i].q,&node[i].v);
        sort(node,node+n,cmp);
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++)
          for(int j=m;j>=node[i].q;j--)
            dp[j]=max(dp[j],dp[j-node[i].p]+node[i].v);
        printf("%d\n",dp[m]);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值