背包资料:参考背包九讲
HDU 2546
思路:卡上的剩余金额大于或等于5元,就一定可以购买成功,注意这句话,那么我们考虑如果我们拿这最后五块钱去买最贵的东西的话肯定是可以的,但此时能选得物品不包括最贵的那一样,因为最贵的那一样我们会拿开始就拿出来的5块钱去买、那么问题就简化成了一个01背包
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int qq=1000+10;
int num[qq];
int dp[qq];
int main()
{
int n;
while(scanf("%d",&n)!=EOF){
if(!n) break;
for(int i=1; i<=n; ++i)
scanf("%d",&num[i]);
int m;scanf("%d",&m);
if(m<5){ //少考虑这个情况
printf("%d\n",m);
continue;
}
m-=5;
memset(dp,0,sizeof(dp));
sort(num+1,num+n+1);
for(int i=1; i<=n-1; ++i)
for(int j=m; j>=num[i]; --j) //从后往前取保证每个数只取一次、
dp[j]=max(dp[j], dp[j-num[i]]+num[i]);
printf("%d\n",m+5-dp[m]-num[n]);
}
return 0;
}
注意理解为何是逆序遍历、想想dp状态的由来
提醒自己要细心,每一种情况都考虑完
HDU 1203
思路:题目要求至少获得一份offer的概率,这个有点不好求,那么我们反过来想,考虑他的反面求一份offer都获得不了的概率,定义状态dp[j]为当钱为j时一份offer也拿不到的最小概率,那么 dp[j] = min(dp[j], dp[j-v]*b) , 变种的01背包问题、
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int qq=1e4+10;
double dp[qq];
int val[qq];
double bility[qq];
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
if(n==0&&m==0) break;
for(int i=0; i<m; ++i){
scanf("%d%lf",&val[i],&bility[i]);
bility[i]=1-bility[i];
}
for(int i=0; i<=n; ++i) dp[i]=1.0;
for(int i=0; i<m; ++i)
for(int j=n; j>=val[i]; --j)
dp[j]=min(dp[j], dp[j-val[i]]*bility[i]);
printf("%.1lf%%\n",(1.0-dp[n])*100);
}
return 0;
}
HDU 2639
参考资料:传送门
题意:01背包求第k解、
思路:定义状态dp[j][k]代表当背包容量为j时第k解、
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int qq = 105;
int n,v,k;
int price[qq],value[qq];
int a[35],b[35];
int dp[qq*10][35];
int main()
{
int t;scanf("%d",&t);
while(t--){
scanf("%d%d%d",&n,&v,&k);
int i,j,l,x,y,z;
for(i=1; i<=n; ++i) scanf("%d",&value[i]);
for(i=1; i<=n; ++i) scanf("%d",&price[i]);
memset(dp, 0, sizeof(dp));
for(i=1; i<=n; ++i)
for(j=v; j>=price[i]; --j){
for(l=1; l<=k; ++l){
a[l]=dp[j][l];
b[l]=dp[j-price[i]][l]+value[i];
}
x=y=z=1;
a[l]=b[l]=-1;
while(z<=k && (a[x]!=-1 || b[y]!=-1)){
if(a[x]>b[y]) dp[j][z]=a[x],x++;
else dp[j][z]=b[y],y++;
if(dp[j][z]!=dp[j][z-1]) z++;
}
}
printf("%d\n",dp[v][k]);
}
return 0;
}
HDU 1114
题意:给出一个存钱罐的为空时的重量和装满时的重量,然后给出n种价值的硬币,价值为 v,重量为w,求装满存钱罐时的最少价值。
思路:完全背包问题,注意范围!!!
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int qq=5510;
const int MAX=1e8;
int price[qq],value[qq];
int dp[100100];
int main()
{
int t;scanf("%d",&t);
while(t--){
int n,m;scanf("%d%d",&n,&m);
int v=m-n;
int k;scanf("%d",&k);
for(int i=0; i<k; ++i)
scanf("%d%d",&value[i],&price[i]);
for(int i=1; i<=v; ++i) dp[i]=MAX;
dp[0]=0;
for(int i=0; i<k; ++i) //这里是k不是n、习惯性的当成n了
for(int j=price[i]; j<=v; ++j) //以后还是不要出现n表示别的东西吧、
dp[j]=min(dp[j], dp[j-price[i]]+value[i]);
if(dp[v]!=MAX) printf("The minimum amount of money in the piggy-bank is %d.\n",dp[v]);
else printf("This is impossible.\n");
}
return 0;
}
HDU 2191
思路:根据背包九讲直接转化成01背包、
注意数据范围!!!
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int qq = 205;
int price[qq*10],value[qq*10];
int dp[qq];
int main()
{
int t;scanf("%d",&t);
while(t--){
int n,m;scanf("%d%d",&n,&m);
int k=0;
int c,a,b;
for(int i=0; i<m; ++i){
scanf("%d%d%d",&a,&b,&c);
int t=1;
while(c>t){
price[k]=t*a;
value[k]=t*b;
c=c-t;
k++;
t<<=1;
}
if(c>0){
price[k]=c*a;
value[k]=c*b;
k++;
}
}
memset(dp, 0, sizeof(dp));
for(int i=0; i<k; ++i)
for(int j=n; j>=price[i]; --j)
dp[j]=max(dp[j], dp[j-price[i]]+value[i]);
printf("%d\n",dp[n]);
}
return 0;
}
HDU 1059
题意:给出价值为1到6的硬币的个数,求是否能把平分这些硬币,使得两个人所获得的钱一样多
思路:还是拆成01背包然后对容量为sum/2的背包进行dp,如果最后dp[sum/2]的状态存在,那么就可以平分
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<stack>
#include<vector>
#include<utility>
using namespace std;
const int qq = 1e5+10;
int price[qq],value[qq];
int dp[qq];
int num[10];
int main()
{
int k=1;
while(scanf("%d",&num[1])!=EOF){
int count=0;
int c=num[1];
for(int i=2; i<=6; ++i){
scanf("%d",&num[i]);
c+=num[i];
}
if(c==0) break;
int sum=0;
for(int i=1; i<=6; ++i){
int t=1;
sum+=i*num[i];
while(num[i]>=t){
price[count]=t*i;
value[count]=t*i;
count++;
num[i]=num[i]-t;
t=t<<1;
}
if(num[i]){
price[count]=i*num[i];
value[count]=i*num[i];
++count;
}
}
printf("Collection #%d:\n",k++);
if(sum%2==1){
printf("Can't be divided.\n\n");
continue;
}
memset(dp, 0, sizeof(dp));
for(int i=0; i<count; ++i)
for(int j=sum/2; j>=price[i]; --j)
dp[j]=max(dp[j], dp[j-price[i]]+value[i]);
if(dp[sum/2]==sum-sum/2) printf("Can be divided.\n");
else printf("Can't be divided.\n");
printf("\n");
}
return 0;
}
HDU 3466
思路:排序后才能看成背包问题、
参考:传送门
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int qq=550;
struct Item
{
int p,q,w;
int t;
bool operator < (const Item a)
{
return t<a.t;
}
}item[qq];
int n,m;
int dp[qq*10]; //注意dp的范围、
int Quicksort(int l, int r)
{
int i=l,j=r;
int x=item[l].t;
Item c=item[l]; //很久没写快排了、 今天一写 错误竟然那么多、
while(i<j){ //感觉我有点死记模版了、 但是思路我确实是知道的、
while(i<j && item[j].t>=x) --j; //下次以思路来代模版、
item[i]=item[j];
while(i<j && item[i].t<=x) ++i;
item[j]=item[i];
}
item[i]=c;
return i;
}
void Quick(int l, int r)
{
if(l<r){
int temp=Quicksort(l,r);
Quick(l,temp);
Quick(temp+1, r);
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF){
for(int i=0; i<n; ++i){
scanf("%d%d%d",&item[i].p,&item[i].q,&item[i].w);
item[i].t=item[i].q-item[i].p;
}
Quick(0,n-1);
//for(int i=0 ;i<n; ++i)
// printf("%d\n",item[i].t);
// sort(item,item+n);
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; ++i)
for(int j=m; j>=item[i].q; --j)
dp[j]=max(dp[j], dp[j-item[i].p]+item[i].w);
printf("%d\n",dp[m]);
}
return 0;
}
POJ 2063
题意:给出本金n,是和要存的年数,然后给出k,代表有k中存钱方式,存期都是一年,你存钱a,一年后可以得到利息b,问m年后能得到的最大利息 + 本金
思路:其实也就是个完全背包,但是这里加了年数,那么对年数进行循环行了,但是这样问题还没解决,TEL, 因为本金是不断变大的, 循环的时间也越来越多,所以我们要想办法压缩时间,The value of a bond is always a multiple of $1 000. 注意这句话,这句话是在input里面, 那么显然我们可以对价值进行压缩。
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int qq=5e4+10;
const int inf=1e9;
int dp[qq],price[110],value[110];
int main()
{
int t;scanf("%d",&t);
while(t--){
int n,m;scanf("%d%d",&n,&m);
int k;scanf("%d",&k);
for(int i=1; i<=k; ++i){
scanf("%d%d",&price[i],&value[i]);
price[i]/=1000; //对背包大小进行压缩、
}
int val=n;
for(int i=1; i<=m; ++i){
int maxn=0;
int s=val/1000; //继续压缩、
memset(dp, 0, sizeof(dp));
for(int j=1; j<=k; ++j)
for(int l=price[j]; l<=s; ++l)
dp[l]=max(dp[l], dp[l-price[j]]+value[j]);
val+=dp[s]; //本金+利息
}
printf("%d\n",val);
}
return 0;
}
POJ 3181
思路:整数划分的dp,这题的结果会溢出long long的范围,用下面的方法优化了
整数划分思想:传送门
参考:kuangbin
#include<cstdio>
#include<cstring>
const int qq=1000+10;
typedef long long ll;
ll a[qq];
ll b[qq];
int main()
{
int n,k;scanf("%d%d",&n,&k);
ll inf=1;
for(int i=0; i<18; ++i) inf*=10;
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
a[0]=1; //优化到一维后注意初状态、
for(int i=1; i<=k; ++i)
for(int j=1; j<=n; ++j){
if(j<i){ //j<i说明j-i是个负数 不存在dp[j-i][i]的情况 ;
continue;
}
b[j]=b[j]+b[j-i]+(a[j]+a[j-i])/inf;
a[j]=(a[j]+a[j-i])%inf;
}
if(b[n]!=0) printf("%I64d%018I64d",b[n],a[n]);
else printf("%I64d\n",a[n]);
return 0;
}
POJ 2184
题意:给出n行数,每一行给出一个si和fi,要求选出任意行使得所有si+fi的总和最大,且si总和是fi的总和都要大于等于0
思路:01背包的变种,参考:kuangbin, 为什么要分正序和逆序呢? 想想开始我们做普通01背包的时候为什么逆序,因为状态更新的由来!!!
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int dp[200010];
int v[110],w[110];
int main(){
int n;
scanf("%d",&n);
for(int i=0; i<n; ++i)
scanf("%d%d",&v[i],&w[i]);
for(int i=0; i<=200000; ++i) dp[i]=-INF;
dp[100000]=0;
for(int i=0; i<n; ++i)
if(v[i]>0){
for(int j=200000; j>=v[i]; --j)
if(dp[j-v[i]]>-INF)
dp[j]=max(dp[j], dp[j-v[i]]+w[i]);
}
else{
for(int j=0; j<=200000+v[i]; ++j)
if(dp[j-v[i]]>-INF)
dp[j]=max(dp[j], dp[j-v[i]]+w[i]);
}
int maxn=0;
for(int i=100000; i<=200000; ++i)
if(dp[i]>=0 && dp[i]+i-100000>maxn)
maxn=dp[i]+i-100000;
printf("%d\n",maxn);
return 0;
}
HDU 2955
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int qq = 105;
int v[qq];
double probli[qq];
double dp[qq*100];
int main()
{
int t;scanf("%d",&t);
while(t--){
double lower;scanf("%lf",&lower);
int n;scanf("%d",&n);
int sum=0;
lower=1.0-lower;
for(int i=0; i<n; ++i){
scanf("%d%lf",&v[i],&probli[i]);
sum+=v[i];
probli[i]=1-probli[i];
}
// 通过这题我知道了初始状态的重要性、
//而且这题的钱数都是一些特定的数、 有些状态可能达不到、比如
//第二组sample input 偷取1钱的安全概率始终是0、 因为这个状态不存在、
//还要记得每个状态都是由前面的状态递推而来、
//所以初始状态真的很重要、
for(int i=1; i<=sum; ++i) dp[i]=0;
dp[0]=1; //当盗取0钱的时候 安全概率肯定是1、
for(int i=0; i<n; ++i){
for(int j=sum; j>=v[i]; --j){
dp[j]=max(dp[j], dp[j-v[i]]*probli[i]);
// printf("%lf ",dp[j]);
}
//printf("\n");
}
int maxn=0;
for(int i=1; i<=sum; ++i)
if(dp[i]>=lower&&maxn<i)
maxn=i;
printf("%d\n",maxn);
}
return 0;
}
HDU 4342
参考:kuangbin
这题真的太妙了、
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int qq = 200 + 10;
int group[qq][qq]; //代表第i组第j个物品的位置、(存的物品是位置)
int dp[40010];
struct Node
{
int x,y;
int v,t;
bool operator < (const Node a) //考虑到斜率x可能为0、所以用乘法来比较
{
if(y*a.x==x*a.y) return y<a.y;
return y*a.x<x*a.y;
}
}node[qq];
int main()
{
int n,m;
int t=1;
while(scanf("%d%d",&n,&m)!=EOF){
for(int i=0; i<n; ++i)
scanf("%d%d%d%d",&node[i].x,&node[i].y,&node[i].t,&node[i].v);
sort(node, node+n);
int kind=0;
for(int i=0; i<n; ++i){
group[kind][0]=0;
group[kind][++group[kind][0]]=i;
int x1=node[i].x;
int y1=node[i].y;
int a=node[i].t;
int b=node[i].v;
for(i++; i<n; ++i){ //进行分组、
int x2=node[i].x;
int y2=node[i].y;
a+=node[i].t;
b+=node[i].v;
if(x1*y2==x2*y1){
group[kind][++group[kind][0]]=i;
node[i].t=a;
node[i].v=b;
}
else{
--i;break;
}
}
kind++;
}
memset(dp, 0, sizeof(dp)); //其实和01背包差不多、
for(int k=0; k<kind; ++k) //预处理起来麻烦一点、
for(int j=m; j>=0; --j)
for(int i=1; i<=group[k][0]; ++i)
if(j-node[group[k][i]].t>=0)
dp[j]=max(dp[j], dp[j-node[group[k][i]].t]+node[group[k][i]].v);
printf("Case %d: %d\n",t++,dp[m]);
}
return 0;
}