背包问题最经典的就是01背包。
n个物品,有体积 w 和价值 v 两种属性,选择一部分填充容量为 m 的背包(可以不填满),求能获得的最大价值。
其实分析下来,动态转移方程就是 f[i][v]=max{f[i-1][v], f[i-1][v-c[i]]+w[i]} ,优化后就是 f[v]=max{f[v], f[v-c[i]]+w[i]} (第二重for循环从m -> v[i],为了不更新i-1层的状态)
这个状态转移方程就会一个贪心过程,不断的用第i个物品更新每个f[j]的值,找到最大的f[j],最终的结果就是最大值。
其实背包除了不一定恰好装满,恰好装满,还有大于等于背包的容量这种状态。像这种大于背包容量的最优解,不会求最大值(求最大值可以一直贪,无穷大),会求大于背包容量的最小花费(中文其实挺好玩的,求最大就说获得的价值,求最小就说是花费),这是一个零界点,具有最优性质在里面,所以用贪心去优化背包。
看一道2017年百度之星的题:
度度熊与邪恶大魔王
对于每组测试数据,输出最小的晶石消耗数量,如果不能击败所有的怪兽,输出-1
这是一道完全背包,但是是n个背包,怪兽的生命力不同,防御值也不同,所以没有办法很好的去建立一个模型。
但是生命值1000种,防御值为10种,一共是10000种怪兽,我们求出这10000种怪兽的花费,在乘以该种类怪兽的数量,累加就是答案。这题的极限应该是O(1e9),但是数据应该不卡时间,所以200ms多就过了。
首先对m种技能进行排序(贪心策略),按防御力从小到大进行排序,防御力相同,花费大的在前(从劣往优排序),两重for,枚举怪物的种类,然后用m种物品更新最小花费。代码如下:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define LL long long
#define P pair<int,int>
using namespace std;
const int maxn=1e5+100,maxm=1e4+10;
int a[maxn],b[maxn];
LL f[maxm];
P c[maxm];
int n,m,ma,mb;
namespace std
{
bool operator < (const pair<int,int> &t,const pair<int,int> &s)
{
return t.second==s.second?(t.first>s.first):(t.second<s.second);
}
}
LL solve()
{
int t=1;
for(int i=2; i<=m; ++i)
{
while(t>0&&c[t].first>c[i].first) --t;
c[++t]=c[i];
}
LL ans=0;
for(int i=0; i<=mb; ++i)
{
for(int j=1; j<=ma; ++j)
{
f[j]=1ll*j*c[t].first;
for(int k=1; k<=t; ++k)
if(c[k].second>i)
f[j]=min(f[j],f[max(0,j+i-c[k].second)]+c[k].first);
}
for(int j=1; j<=n; ++j)
if(b[j]==i) ans+=f[a[j]];
}
return ans;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
ma=0,mb=0;
for(int i=1; i<=n; ++i)
{
scanf("%d%d",&a[i],&b[i]);
if(a[i]>ma) ma=a[i];
if(b[i]>mb) mb=b[i];
}
for(int i=1; i<=m; ++i)
scanf("%d%d",&c[i].first,&c[i].second);
sort(c+1,c+m+1);
if(c[m].second<=mb)
{
printf("-1\n");
continue;
}
/*
for(int i=1;i<=m;++i)
printf("%d %d\n",c[i].first,c[i].second);
*/
printf("%I64d\n",solve());
}
return 0;
}