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 1≤N≤100)种货币参与流通,面值分别为 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 1≤Vi≤120)。John 有 C i C_i Ci 个面值为 V i V_i Vi 的硬币( 0 ≤ C i ≤ 1 0 4 0 \le C_i \le 10 ^ 4 0≤Ci≤104)。
我们假设店主有无限多的硬币, 并总按最优方案找零。注意无解输出-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
T≤x),则店家要给john找
x
−
T
x-T
x−T 元钱,那我们就可以枚举一下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;
}