/*
有关这个问题画了太多时间了;
对自己真的很失望,思维能力太差了;
庆幸的是自己坚持看懂了;
希望下次碰到能够灵活运用;
*/
题目其实可以转换成背包问题;
由于数据是-100000~100000;
所以我们取数组 dp[100000*2];
我们产生一个相对坐标的概念,100000相对坐标为0;99995相对坐标为-5;x的相对坐标为x-100000;
由于每组数据的属性都为价值;
而对于背包的容量是无限扩展的,(题目要求的就是 背包最大能装多少价值的东西)
我们对此做个变形,对于smartness (S),funness(F),将S总和作为背包的容量(可扩展),将F总和作为背包物品的价值;
每考虑一个物品(s,f)的加入与否,如果假如则背包容量也相应增加s;
于是状态转移方程为
dp[i+MAX+s] = max(dp[i+MAX+s] , dp[i+MAX]+f); //MAX = 100000
当然对于s>0和s<0时进行动态规划时,for循环的顺序要注意;
当s>0时;for循环要从_max 到 _min
当s<0时;for循环要从 _min到 _max
其道理和我们做01背包时候第二个for循环逆序的道理一样;
s<0时,更新的是相对目前这些数据的左边的数据,所以我们应该从_max-->_min
s>0时,更新的是相对目前这些数据的右边的数据,所以我们应该从 _min-->_max
细细体会~~~
//Problem: 2184 User: fuqiang
//Memory: 1680K Time: 63MS
//Language: G++ Result: Accepted
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdio>
#include <cstdlib>
using namespace std;
#define MAX 100000
#define INF 999999999
bool visit[MAX*2+5];
int dp[MAX*2+5]; //dp[i+MAX]总的s值为i时的最优的f值(其实就是说两个都是和的形式,因为选了f就选了那个s)
int main()
{
memset(visit,0,sizeof(visit));
for(int i = 0; i < MAX*2+5; i++)
dp[i] = -INF;
dp[0+MAX] = 0; //初始化,一组数据都没取的时候,结果为0;
visit[0+MAX] = 1; //初始化,已有结果为0,标记为1;这个0相当于0+MAX
int T,_max = 0,_min = 0; //_max , _min 为dp范围,范围-MID~MID
scanf("%d",&T);
int s,f;
while(T--)
{
scanf("%d%d",&s,&f);
if(s<=0&&f<=0) //剪支;只有一个小于或等于0不能被剪
continue;
if(s>0)
{
for(int i = _max; i >= _min; i--) //如果s>0则从大到小,否则从小到大扫描,会产生后效性(重复计算)
{
if(visit[i+MAX]) //如果vis[MID+i]已经标记为1,则此次策略会影响dp[MID+i];否则不影响
if(dp[i+MAX+s]<dp[i+MAX]+f) //表示该Cow可取
{
dp[i+MAX+s] = dp[i+MAX]+f;
visit[i+MAX+s] = 1;
}
}
_max += s;
}
else
{
for(int i = _min; i <= _max; i++) //如果s<0则从小到大,否则从大到小扫描,会产生后效性(重复计算)
{
if(visit[i+MAX])
if(dp[i+MAX+s]<dp[i+MAX]+f)
{
dp[i+MAX+s] = dp[i+MAX]+f;
visit[i+MAX+s] = 1;
}
}
_min += s;
}
}
int ans = 0;
for(int i = 0; i <= _max; i++) //剪掉了i<0的情况
{
if(visit[i+MAX])
if(dp[i+MAX]>0 && dp[i+MAX]+i > ans) //不能漏掉dp[i+MAX]>0,否则会出现虽然总数达到最大,
ans = dp[i+MAX]+i; //但f总和小于0的情况;如题目HINT
}
printf("%d\n",ans);
}
这题还可以直接转换成01背包的思想;(你可以对比时间发现时间比上面的慢了一半)
转台转移方程也和01背包的相同:
dp[j] = max(dp[j-S[i]]+F[i],dp[j]);
当然同样要注意 顺序和逆序;
转换01背包代码:
//Problem: 2184 User: fuqiang
//Memory: 1484K Time: 110MS
//Language: G++ Result: Accepted
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdio>
#include <cstdlib>
using namespace std;
#define MAX 100000
#define N 100
#define INF 999999999
int dp[MAX*2+5]; //dp[i+MAX]总的s值为i时的最优的f值(其实就是说两个都是和的形式,因为选了f就选了那个s)
int S[N+5],F[N+5];
int main()
{
int n,s,f,p = 0;
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&s,&f);
if(!(s<=0&&f<=0))
{
S[p] = s;
F[p++] = f;
}
}
n = p;
for(int i = 0; i <= MAX*2; i++)
dp[i] = -INF;
int _min = MAX;
dp[MAX] = 0;
for(int i = 0; i < n; i++)
{
if(S[i]<0) _min += S[i];
if(S[i]>0)
{
for(int j = MAX*2; j >= _min; j--)
{
if(dp[j-S[i]]!=-INF)
dp[j] = max(dp[j-S[i]]+F[i],dp[j]);
}
}
else
{
for(int j = _min; j <= MAX*2+S[i]; j++)
{
if(dp[j-S[i]]!=-INF)
dp[j] = max(dp[j-S[i]]+F[i],dp[j]);
}
}
}
int ans = 0;
for(int i = MAX; i <= MAX*2; i++) //剪掉了i<MAX的情况,即相对值i<0的情况;
{
//if(dp[i]!=-INF)
if(dp[i]>0 && dp[i]+i-MAX > ans) //不能漏掉dp[i]>0,否则会出现虽然总数达到最大,
ans = dp[i]+i-MAX; //但f总和小于0的情况;如题目HINT
}
printf("%d\n",ans);
}