高手专项训练-背包问题

A. 排兵布阵

【题目描述】

小 A 正在玩一款排兵布阵的游戏。在游戏中有 n n n 座城堡,每局对战由两名玩家来争夺这些城堡。每名玩家有 m m m 名士兵,可以向第 i i i 座城堡派遣 a i a_i ai 名士兵去争夺这个城堡,使得总士兵数不超过 m m m

如果一名玩家向第 i i i 座城堡派遣的士兵数严格大于对手派遣士兵数的两倍,那么这名玩家就占领了这座城堡,获得 i i i 分。

现在小 A 即将和其他 s s s 名玩家两两对战,这 s s s 场对决的派遣士兵方案必须相同。小 A 通过某些途径得知了其他 s s s 名玩家即将使用的策略,他想知道他应该使用什么策略来最大化自己的总分。

由于策略可能不唯一,你只需要输出小 A 总分的最大值。

【输入格式】

输入第一行包含三个正整数 s , n , m s,n,m s,n,m,分别表示除了小 A 以外的玩家人数、城堡数和每名玩家拥有的士兵数。

接下来 s s s 行,每行 n n n 个非负整数,表示一名玩家的策略,其中第 i i i 个数 a i a_i ai 表示这名玩家向第 i i i 座城堡派遣的士兵数。

【输出格式】

输出一行一个非负整数,表示小 A 获得的最大得分。

【样例】
【输入样例】
1 3 10
2 2 6
【输出样例】
3

想法

有点像分组背包,可以对每一座城堡的对手士兵数进行排序,从小到大判断,派兵多的一定能覆盖派兵少的对手,然后直接跑背包就好了。

【代码】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{
	int w[101];
}dis[101];
int s,n,m,f[101][20001],ans;
int main(){
	//freopen("battle.in","r",stdin);
	//freopen("battle.out","w",stdout);
	scanf("%d%d%d",&s,&n,&m);
	for(int i=1;i<=s;++i)
	    for(int j=1;j<=n;++j)
	        scanf("%d",&dis[j].w[i]);
	for(int i=1;i<=n;++i)
	    sort(dis[i].w+1,dis[i].w+1+s);//排序
	for(int i=1;i<=n;++i)
	    for(int j=m;j>0;--j){//背包板子
	    	for(int k=1;k<=s;++k){
	        	if(dis[i].w[k]*2+1<=j)
	        	    f[i][j]=max(f[i][j],f[i-1][j-(dis[i].w[k]*2+1)]+k*i);
			}
			f[i][j]=max(f[i][j],f[i-1][j]);
		}
   for(int i=1;i<=m;++i)
       ans=max(f[n][i],ans);
	printf("%d\n",ans);
	return 0;      
}

B. 最优方案(洛谷P2851)

【题目描述】

农夫 John 想到镇上买些补给。为了高效地完成任务,他想使硬币的转手次数最少。即使他交付的硬币数与找零得到的的硬币数最少。

John 想要买价值为 T T T 的东西。有 N N N 1 ≤ N ≤ 100 1 \le N \le 100 1N100)种货币参与流通,面值分别为 V 1 , V 2 , … , V N V_1,V_2,\dots,V_N V1,V2,,VN 1 ≤ V i ≤ 120 1 \le V_i \le 120 1Vi120)。John 有 C i C_i Ci 个面值为 V i V_i Vi 的硬币( 0 ≤ C i ≤ 1 0 4 0 \le C_i \le 10 ^ 4 0Ci104)。

我们假设店主有无限多的硬币, 并总按最优方案找零。注意无解输出-1

【输入格式】

第一行两个整数 N , T N,T N,T
第二行 N N N 个整数 V 1 , V 2 , … , V N V_1,V_2,\dots,V_N V1,V2,,VN
第三行 N N N 个整数 C 1 , C 2 , … , C N C_1,C_2,\dots,C_N C1,C2,,CN

【输出格式】

一行一个整数,表示最少的交付硬币数与找零得到的硬币数,若无解则输出 -1

【样例】
【输入样例】
3 70
5 25 50
5 2 1
【输出样例】
3

想法

朴实无华的想法得, 当John付了 x x x 元钱( T ≤ x T \le x Tx),则店家要给john找 x − T x-T xT 元钱,那我们就可以枚举一下john付了多少钱,然后将两个方面的问题分开,对john跑多重背包,对店家跑完全背包,寻找最小值。

问题变成了应该枚举到多少才能保证问题的正确性,(我这里学习了一下),具体看图片
(选取了洛谷题解区hkr04的证明,膜拜)
记得要进行二进制优化,不然会炸

【代码】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int f[40000],g[40000],n,t;
int v[1001],ge[1001],ans;
int main(){
	//freopen("plan.in","r",stdin);
	//freopen("plan.out","w",stdout);
	scanf("%d%d",&n,&t);
	int mx=0,sum=0;
	for(int i=1;i<=n;++i){
		scanf("%d",&v[i]);
		mx=max(mx,v[i]*v[i]);
	}
	for(int i=1;i<=n;++i){
		scanf("%d",&ge[i]);
		sum+=v[i]*ge[i];
	}
	if(sum<t){//特判钱数够不够,如果不够自然要输出-1
		cout<<-1<<endl;
		return 0;
	}
	memset(f,0x3f,sizeof(f));
	memset(g,0x3f,sizeof(g));
	g[0]=f[0]=0;
	for(int i=1;i<=n;++i)
	    for(int j=v[i];j<=mx;++j)
	        f[j]=min(f[j],f[j-v[i]]+1);
	for(int i=1;i<=n;++i){
	    for(int j=1;j<=ge[i];j*=2){
	        for(int k=t+mx;k>=j*v[i];--k)//二进制优化
	        	g[k]=min(g[k],g[k-j*v[i]]+j);
	        ge[i]-=j;
		}
		if(ge[i])
		    for(int k=t+mx;k>=v[i]*ge[i];--k)
		        g[k]=min(g[k],g[k-ge[i]*v[i]]+ge[i]);
	}
	ans=0x3fffffff; //开大点,但不要开太大,不然会出现假答案
	for(int i=t;i<=t+mx;++i)
	    ans=min(ans,g[i]+f[i-t]);
	if(ans==0x3fffffff) cout<<-1<<endl;
	else cout<<ans;
	return 0;
}
  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值