dp训练第19题 hdu4341. Gold miner 分组背包 前缀和

黄金矿工游戏,有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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值