UVA 10163 Storage Keepers (01背包DP + 二分)

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">题意:</span>

有n个仓库(最多100个),m个管理员(最多30个),每个管理员有一个能力值P(接下来的一行有m个数,表示每个管理员的能力值)

每个仓库只能由一个管理员看管,但是每个管理员可以看管k个仓库(但是这个仓库分配到的安全值只有p/k,k=0,1,...),

每个月公司都要给看管员工资,雇用的管理员的工资即为他们的能力值p和,问,使每个仓库的安全值最高的前提下,使的工资总和最小。

输出最大安全值,并且输出最少的花费。


思路:

看别人的是用两次DP 我用了二分求最大的最小安全系数 然后就是一个01背包了


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int INF=2147483647;
int n,m,sum,w[50],v[50],d[100005];
int p[50],cnt,cnt1;
struct Node{
    int p,k,an,id;
    bool operator < (const Node &a) const {
        return an < a.an;
    }
}temp[100000];
int add(int p,int k,int &cnt,int id)
{
    temp[cnt].p=p;
    temp[cnt].k=k;
    temp[cnt].id=id;
    temp[cnt].an=p/k;
    cnt++;
}
int pan(int m)
{
    int vis[50]={0},he=0;
    for(;m<cnt;m++){
        if(vis[temp[m].id]) continue;
        he+=temp[m].k;
        vis[temp[m].id]=1;
        if(he>=n) return 1;
    }
    return 0;
}
void init(int s,int &he)
{
    int vis[50]={0};
    he=0;
    for(int i=s;i<cnt;i++){
        if(vis[temp[i].id]) continue;
        vis[temp[i].id]=1;
        he+=temp[i].k;
        w[cnt1]=temp[i].k;
        v[cnt1]=-temp[i].p;          //令背包物品的价值为负值 这样求出来的最大值的相反数就是最小值
        cnt1++;
    }
}
void solve(int r)
{

    int ans=INF;
    for(int i=0;i<=sum;i++) d[i]=-INF;
    d[0]=0;

    for(int i=0;i<cnt1;i++)
        for(int j=sum;j>=w[i];j--)
            if(d[j-w[i]]!=-INF) d[j]=max(d[j],d[j-w[i]]+v[i]);
    for(int i=n;i<=sum;i++)
        ans=min(ans,-d[i]);
    printf("%d %d\n",temp[r].an,ans);
}
int main()
{
    while(scanf("%d%d",&n,&m))
    {
        if(!n&&!m) break;
        cnt=sum=cnt1=0;
        for(int i=0;i<m;i++) {
            scanf("%d",&p[i]);
            sum+=p[i];
            int t=min(p[i],n);
            add(p[i],t,cnt,i);
            for(int j=t-1;j>=1;j--){
                if(p[i]/j==temp[cnt-1].an) continue;
                add(p[i],j,cnt,i);
            }
        }
        if(sum<n) { printf("0 0\n"); continue; }

        sort(temp,temp+cnt);
        int l=0,r=cnt-1;
        while(l<=r){
            int m=l+(r-l)/2;
            if(pan(m)) l+=1;
            else r-=1;
        }
        for(l=r;l>=0&&temp[l].an==temp[r].an;l--){}  //这里应该找符合条件的最左边的那个
        r=temp[l].an==temp[r].an?l:l+1;

        init(r,sum);
        solve(r);
    }
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值