黄金矿工游戏,有n个可挖的金子和v的可用时间.每个金子有坐标x,y,价值value[i],所需时间为volume[i].每个金子的大小不计,但是可能存在一条过原点直线上有多个金子的情况,得先挖了近的才能挖远的.
初看的时候根本想不到该怎么做,后来网上翻了翻大佬的题解,被一个trick震慑了.(也可能是我比较笨没想出来)
按斜率->距离排序,排序之后把相同斜率的分在一组.
注意可解的分组背包题是一组只能选一个或者不选.
而现在的情况是一组可以选多个,但是只能选了前面的再选后面的.
即可以选择前i件.
如何将新的情况转换到已知的有解的情况呢?
用算法.
将这组中的每件的价值和体积(所需时间)都加上这组中之前的件的价值体积,即做了一个前缀和.
这样处理后,只要从前缀和序列里面选择一个,就相当于选择了前i件了.多重背包二进制优化正确性的证明过程同样可以在这里证明正确性:01背包总是选择最优解.
分组过程中我使用的是二重vector.vector取最后一个元素的函数是back().
注意排除非法的转移.
/* LittleFall : Hello! */
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read();
inline void write(int x);
const int N = 256,V = 65536;
struct Item{
int x,y,volume,value;
bool operator < (const Item & b) const
{
return y*b.x==x*b.y?(x*x+y*y)<(b.x*b.x+b.y*b.y):y*b.x<x*b.y;
}
}save[N];
vector<vector<Item>> part;
int dp[V];
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
//std::cin.sync_with_stdio(false);
int n,v,kase=1;
while(scanf("%d%d",&n,&v)!=EOF)
{
memset(dp,0,sizeof(dp));
part.clear();
for(int i=0;i<n;i++)
scanf("%d%d%d%d",&save[i].x,&save[i].y,
&save[i].volume,&save[i].value);
sort(save,save+n);
vector<Item> tmp{save[0]};
for(int i=1;i<n;i++)
{
if(save[i].y*tmp.back().x!=save[i].x*tmp.back().y)
{
part.push_back(tmp);
tmp.clear();
tmp.push_back(save[i]);
continue;
}
save[i].volume+=save[i-1].volume;
save[i].value+=save[i-1].value;
tmp.push_back(save[i]);
}
part.push_back(tmp);
for(vector<Item> k:part)
for(int j=v;j>=0;j--)
for(Item i:k)
if(j>=i.volume)
dp[j]=max(dp[j],dp[j-i.volume]+i.value);
printf("Case %d: %d\n",kase++,dp[v] );
}
return 0;
}