题意:
牛类想要向世人证明他们聪明又幽默。经过测试,每头牛都有一个幽默度Fi和智商Si,现要求从N头牛中选择若干头牛去参加比赛,假设这若干头牛的智商之和为sumS,幽默度之和为sumF.现要求在所有选择中,在使得sumS>=0&&sumF>=0的基础上,使得sumS+sumF最大并输出其值.
分析:
01背包dp之后把符合要求的最优解统计出来。但是在解01背包的时候遇到一个问题是体积有负数,这样在dp的过程中会遇到两个问题:循环的时候超出体积的范围;
压缩空间的时候状态转移方程:dp[v]=max(dp[v],dp[v-c[i]]+w[i]),c[i]为负数时v-c[i]>v,这样按一般的循环的方向从大到下会重复计算。
先看第二个问题,在一般的01背包压缩空间的时候,体积的遍历是从大到小,因为dp[v]=max(dp[v],dp[v-c[i]]+w[i]),当前的dp[v]只取决于比自己小的dp[v-c[i]],所以从大到小遍历时每次dp[v-c[i]]和dp[v]都是上一次的状态。
如果体积为负v-c[i]>v,从大到小遍历dp[v-c[i]]是当前物品的状态,不是上一个,这样就会出错,解决的办法是从小到大遍历。
针对第一个问题,在处理的时候将整个数轴平移,使得原来所有可能的情况都为正。
例如这题,首先计算出数据的范围:
一共100组数,从-1000到1000,那么体积的范围就是-100*1000到100*1000。平移之后我们要处理的数据范围就在0到200000,新的原点变成100000。
#include <limits.h>
#define V 200*1010
#define N 105
#define max(a,b) ((a)>(b)?(a):(b))
int cost[N] ;
int value[N] ;
int dp[V+5] ;
void DP_Solve ( int const n ) ;
void Init_DP ( ) ;
int
main ( )
{
int n ;
scanf ("%d" , & n ) ;
int i ;
for ( i = 1 ; i <= n ; i ++ )
{
scanf ("%d%d" , & cost[i] , & value[i] ) ;
}
Init_DP ( ) ;
DP_Solve ( n ) ;
return 0 ;
}
void Init_DP ( )
{
int i ;
for ( i = 0 ; i <= V ; i ++ )
{
dp[i] = INT_MIN ;
}
dp[100*1000] = 0 ;
}
void DP_Solve ( int const n )
{
int i ;
for ( i = 1 ; i <= n ; i ++ )
{
if ( cost[i] > 0 )
{
int j ;
for ( j = V ; j >= cost[i] ; j -- )
{
if ( dp[j-cost[i]] > INT_MIN )
{
dp[j] = max ( dp[j] , dp[j-cost[i]] + value[i] ) ;
}
}
}
else
{
int j ;
for ( j = cost[i] ; j - cost[i] < V ; j ++ )
{
if ( dp[j-cost[i]] > INT_MIN )
{
dp[j] = max ( dp[j] , dp[j-cost[i]] + value[i] ) ;
}
}
}
}
int m_max ;
m_max = 0 ;
for ( i = 100 * 1000 ; i <= V ; i ++ )
{
if ( dp[i] >= 0 ) //注意不能写成 if (dp[i]) 会导致INT_MIN + i - 100 * 1000 溢出
{
m_max = max ( m_max , dp[i] + i - 100 * 1000 ) ;
}
}
printf ("%dn" , m_max ) ;
}