UVA - 10163 Storage Keepers(二分答案+01背包)

题目链接


首先感谢洛谷大佬rayluo的详细题解,万分感激%%%

整个问题分为二分和DP两部分

二分

看到最小的最大、最大的最小这类字眼就需要考虑二分答案。假设我们二分出来的值为 x x x。为了使 x x x可行,也就是说每一个仓库的安全值都要大于等于 x x x。而我们注意到,在这个式子中 p i p_i pi为定值, k k k越大,式子的值越小,那么在 p i k ≥ x {\frac{p_i}{k} \geq x} kpix 的情况下, k k k是有最大值的,为 p i x { \frac{p_i}{x} } xpi,我们只需要使得每一个守卫的 k k k的最大值之和大于等于 n n n,那么这个 x x x就是一个可行解,即:

bool check(int x){
	if(x==0) return true;
	int cnt=0;
	for(int i=1;i<=m;i++){
		cnt+=(p[i]/x);
		if(cnt>=n) return true;
	}
	return false;
}

DP

我们将每个守卫看做物品,其雇佣金为价值。背包的容量为仓库的个数,那么每个人选或不选构成一个01背包的问题,但是因为每个人可能看守多个仓库,因此还需要增加一维。于是 d [ i ] [ j ] d[i][j] d[i][j]代表考虑到第 i i i个守卫,前 i − 1 i-1 i1个守卫已经考虑过,一共守卫的仓库个数为 j j j。此外还要枚举 k k k k k k是第 i i i个守卫保护的仓库数量。 x x x是最小的最大安全系数,那么有:

d [ i ] [ j ] = m i n ( d [ i ] [ j ] , d [ i − 1 ] [ j − k ] + p [ i ] ) , p [ i ] / k ≥ x d[i][j]=min(d[i][j],d[i-1][j-k]+p[i]),p[i]/k \geq x d[i][j]=min(d[i][j],d[i1][jk]+p[i]),p[i]/kx

初始为 d [ 0 ] [ 0 ] = 0 d[0][0]=0 d[0][0]=0;最后的答案是 d [ i ] [ n ] , 1 ≤ i ≤ m d[i][n],1 \leq i \leq m d[i][n],1im

易错点

另外一开始我写的是:

d[0][0]=0;
for(int i=1;i<=m;i++)
	for(int j=1;j<=n;j++){
		d[i][j]=d[i-1][j];
		for(int k=1;k<=j;k++)
			if(p[i]/k>=x) d[i][j]=min(d[i][j],d[i-1][j-k]+p[i]);
	}

这样之所以会WA,因为我们的边界状态为 d [ 0 ] [ 0 ] d[0][0] d[0][0],但是 d [ 1 ] [ 1 ] d[1][1] d[1][1]这个状态初始化为 d [ 1 ] [ 1 ] = d [ 0 ] [ 1 ] d[1][1]=d[0][1] d[1][1]=d[0][1],显然是不对的,因此 j j j要从0开始枚举。当然如果滚动数组优化后就没有这个忧患了

朴素01背包代码

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <math.h>
#include <cstdio>
#include <string>
#include <bitset>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define lowbit(x) (x&(-x))
#define mkp(x,y) make_pair(x,y)
#define mem(a,x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> P;
const double eps=1e-8;
const double pi=acos(-1.0);
const int inf=0x3f3f3f3f;
const ll INF=1e18;
const int Mod=1e9+7;
const int maxn=2e5+10;

int n,m;
int p[maxn],d[35][105];

bool check(int x){
	if(x==0) return true;
	int cnt=0;
	for(int i=1;i<=m;i++){
		cnt+=(p[i]/x);
		if(cnt>=n)return true;
	}
	return false;
}

int dp(int x){
    if(x==0) return 0;
    memset(d,inf,sizeof d);
    d[0][0]=0;
    for(int i=1;i<=m;i++)
        for(int j=0;j<=n;j++){
            d[i][j]=d[i-1][j];
            for(int k=1;k<=j;k++)
                if(p[i]/k>=x) d[i][j]=min(d[i][j],d[i-1][j-k]+p[i]);
        }
    int ans=inf;
    for(int i=1;i<=m;i++)
        ans=min(ans,d[i][n]);
    return ans;
}

int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    while(cin>>n>>m && n){
        int l=0,r=0,ans=0;
        for(int i=1;i<=m;i++){
            cin>>p[i];
            r=max(r,p[i]);
        }
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid)){
                l=mid+1;
                ans=mid;
            }else r=mid-1;
        }
        cout<<ans<<" "<<dp(ans)<<endl;
    }
    return 0;
}

滚动数组优化代码

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <math.h>
#include <cstdio>
#include <string>
#include <bitset>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define lowbit(x) (x&(-x))
#define mkp(x,y) make_pair(x,y)
#define mem(a,x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> P;
const double eps=1e-8;
const double pi=acos(-1.0);
const int inf=0x3f3f3f3f;
const ll INF=1e18;
const int Mod=1e9+7;
const int maxn=2e5+10;

int n,m;
int p[maxn],d[105];

bool check(int x){
	if(x==0) return true;
	int cnt=0;
	for(int i=1;i<=m;i++){
		cnt+=(p[i]/x);
		if(cnt>=n)return true;
	}
	return false;
}

int dp(int x){
    if(x==0) return 0;
    memset(d,inf,sizeof d);
    d[0]=0;
    for(int i=1;i<=m;i++)
        for(int j=n;j>=1;j--){
            for(int k=1;k<=j;k++)
                if(p[i]/k>=x) d[j]=min(d[j],d[j-k]+p[i]);
        }
    return d[n];
}

int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    while(cin>>n>>m && n){
        int l=0,r=0,ans=0;
        for(int i=1;i<=m;i++){
            cin>>p[i];
            r=max(r,p[i]);
        }
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid)){
                l=mid+1;
                ans=mid;
            }else r=mid-1;
        }
        cout<<ans<<" "<<dp(ans)<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值