20200404(背包)

1、搭配购买

一道并查集加上01背包的题目,通过并查集的方式将属于一个集合的物品封装成一个物品,然后就跟01背包做法相似。

【问题描述】
Joe 觉得云朵很美,决定去山上的商店买一些云朵。商店里有 n 朵云,云朵被编
号为 12,…...,n,并且每朵云都有一个价值。但是商店老板跟他说,一些云
朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。
但是 Joe 的钱有限,所以他希望买的价值越多越好。
【输入说明】
第 1 行 n,m,w,表示 n 朵云,m 个搭配,Joe 有 w 的钱。
第 2~n+1 行,每行 ci,di 表示 i 朵云的价钱和价值。
第 n+2~n+1+m 行,每行 ui,vi,表示买 ui 就必须买 vi,同理,如果买 vi 就必须
买 ui。【输出说明】
一行,表示可以获得的最大价值。
【输入样例】
5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2
【输出样例】
1
【数据范围】
30%的数据保证:n<=100
50%的数据保证:n<=1,000;m<=100;w<=1,000
100%的数据保证:n<=10,0000<=m<=5000;w<=10,000
搭配购买
#include <bits/stdc++.h>
using namespace std;
const int maxx=1e4+5;
int c[maxx],d[maxx],f[maxx];
int fa[10005],pri[10005],val[10005];
int find(int x)
{
    return (fa[x]!=x)?fa[x]=find(fa[x]):x;
}
int main()
{
    int n,m,w,u,v;
    scanf("%d %d %d",&n,&m,&w);
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d",&c[i],&d[i]);
        fa[i]=i;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&u,&v);
        int x=find(u);
        int y=find(v);
        if(x!=y) fa[x]=y;
    }
    for(int i=1;i<=n;i++)
    {
        int x=find(i);
        pri[x]+=c[i];//同一个集合里面的总费用
        val[x]+=d[i];//总价值 
    }
    int count=0;
    for(int i=1;i<=n;i++)
    {
        if(fa[i]==i)
        {
            pri[++count]=pri[i];
            val[count]=val[i];
        }
    }//封装成一个物品
    for(int i=1;i<=count;i++)
        for(int j=w;j>=pri[i];j--)
            f[j]=max(f[j],f[j-pri[i]]+val[i]);//01背包
    printf("%d",f[w]);
    return 0;
}
View Code

2、Dima and Salad

n个物品,k为倍数。每个物品有两个属性(ai和bi),求在满足所取物品的a属性和是b属性和的k倍的前提下,问a属性的最大值是多少

按照题意,就是要让我们选出一些组aibi,使得:

(a1+a2+...+aj)/(b1+b2+...+bj=k

然后移项,得a1+a2+...+aj=k(b1+b2+...+bj)

(a1b1k)+(a2b2k)+...+(ajbjk)=0(a1−b1k)+(a2−b2k)+...+(aj−bjk)=0

观察公式,发现这其实是一个容量为0的01背包,

我们可以把aibik看作一个物品的体积,ai看作价值,然后一个标准的01背包模板

那么,背包的容量?容量为0怎么枚举呢?

先来想一想,(aibik)是不是有可能为负数?那么怎么办呢?

可以考虑开两个背包,容量分别为V和-V,那么加起来就抵消为0,正容量的背包处理正体积的,负容量的背包处理负体积的

【问题描述】
在冰箱里有 n 个水果,每种水果都有两个参数:味道和热量。现在要从冰箱里拿
些水果出来做一份水果沙拉。选择水果时遵循一定的原则:所选水果的总味道与
总卡路里的比率必须等于 k。
请计算在这种原则下,所选水果的最大味道值是什么?
【输入说明】
输入的第一行包含两个整数 n,k(1 ≤ n ≤ 100, 1 ≤ k ≤ 10)。
输入的第二行包含 n 个整数 a1, a2, …, an(1 ≤ ai ≤ 100),表
示每种水果的味道值。
输入的第三行包含 n 个整数 b1, b2, …, bn(1 ≤ bi ≤ 100),表
示每种水果的卡路里,ai 和 bi 是一一对应的。
【输出说明】
如果无法选择沙拉所需的水果,请在第 1 行输出-1。
否则输出一个整数,即所选水果味道值的最大可能总和。
【输入样例 13 2
10 8 1
2 7 1
【输出样例 118
【输入样例 25 3
4 4 4 4 4
2 2 2 2 2
【输出样例 2-1
Dima and Salad
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const int inf = 1e8;
const int maxx=10000;
int f[N],g[N]; 
int a[110],b[110];
int n,k; 
int main()
{
    int num; 
    scanf("%d%d",&n,&k); 
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]); 
    for(int i=1;i<=maxx;i++) f[i]=g[i]=-inf ; 
    for ( int i=1;i<=n;i++ ) {
        num=a[i]-b[i]*k; 
        if(num>=0) {
            for(int j=maxx;j>=num;j--) f[j]=max(f[j],f[j-num]+a[i]);//为正数的时候 
        }
        else{
            for(int j=maxx;j>=-num;j--) g[j]=max(g[j],g[j+num]+a[i]);//为负数的时候 
        }
    }
    int ans=-1; 
    for(int i=maxx;i>=0;i-- ) 
        ans=max(ans,f[i]+g[i]); 
    if(ans<=0) ans=-1 ;
    cout<<ans<<endl; 
    return 0; 
}
View Code

3、最佳策略

其实就是一个分组背包

【问题描述】
经历了无数次编程竞赛失败以后,小明明白了一个道理:做一题就要对一题!但
是要完全正确地做对一题是要花很多时间(包括调试时间),而竞赛的时间有限。
所以开始做题之前最好先认真审题,估算一下每一题如果要完全正确地做出来所
需要的时间,然后选择有把握的题目先做。如果做完了预先选择的题目之后还
有时间,但是这些时间又不足以完全解决一道题目,应该把其他的题目用贪心之
类的算法随便做做,争取“骗”一点分数。
【输入说明】
第 1 行,两个正整数 N 和 T,表示题目的总数以及竞赛的时限(秒)
以下的 N 行,每行四个正整数 W1、T1、W2、T2,分别表示第 i 题完全正确做
出来的得分,完全正确做出来所花费的时间(秒),“骗”来的分数,“骗”分所
花费的时间(秒)。
【输出说明】
根据每一题解题时间的估计值,确定一种做题方案(即哪些题目认真做,哪些题
目“骗”分,哪些不做),使能在限定的时间内获得最高的得分。
【数据范围】
3≤N≤302≤T≤1.08×10^61≤W1i、W2i≤3×10^41≤Tli、T2i≤T
【输入样例 14 10800
18 3600 3 1800
22 4000 12 3000
28 6000 0 3000
32 8000 24 6000
【输出样例 150
【输入样例 23 7200
50 5400 10 900
50 7200 10 900
50 5400 10 900
【输出样例 270
最佳策略
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
long long f[N],s[35][4],w[35][4];
int n,m;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d %d %d %d",&s[i][1],&w[i][1],&s[i][2],&w[i][2]);
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--)
              for(int k=1;k<=2;k++){
                  if(j>=w[i][k]){
                    f[j]=max(f[j],f[j-w[i][k]]+s[i][k]);
          }
      }
    cout<<f[m]<<endl;
    return 0;
}
View Code

4、升级游戏

就是一个多组数据的二维费用背包,状态表示为f[i][j]:kill i个怪 消耗的厌恶值不超过j的最大经验值

【问题描述】
最近有一款叫做 FATE 的游戏,Joe 为了得到极品装备,他需要不停的杀怪做任
务。久而久之开始对杀怪产生厌恶感,但又不得不通过杀怪来升完这最后一级。
现在的问题是,升掉最后一级还需 n 的经验值,他还留有 m 的忍耐度,每杀一
个怪会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到 0 或者 0 以下时,
他就不会玩这游戏。Joe 还说了他最多只杀 s 只怪。请问他能升掉这最后一级吗?
【输入说明】
输入数据有多组,对于每组数据第一行输入 n,m,k,s(0 < n,m,k,s < 100)四个正
整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下
来输入 k 行数据。每行数据输入两个正整数 a,b(0 < a,b < 20);分别表示杀掉一
只这种怪会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
【输出说明】
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。
【输入样例】
10 10 1 10
1 1
10 10 1 9
1 1
9 10 2 10
1 1
2 2
【输出样例】
0
-1
1
升级游戏
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int f[N][N],a[N],b[N];
int n,m,k,s;
int main()
{     
    while(cin>>n>>m>>k>>s){
        memset(f,0,sizeof(f));
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for(int i=1;i<=k;i++){
            scanf("%d %d",&a[i],&b[i]);
        }
        for(int i=1;i<=k;i++)
            for(int j=1;j<=s;j++)//做多能杀的怪物
                for(int l=b[i];l<=m;l++)//因为每种怪物无限个,完全背包
                    f[j][l]=max(f[j][l],f[j-1][l-b[i]]+a[i]);
        int ans=-1;
        for(int i=0;i<=m;i++){
            if(f[s][i]>=n)
            {
                ans=i;
                break;
            }
        }
        if(ans==-1) printf("-1\n");
        else printf("%d\n",m-ans);
    }
    return 0;
}
View Code

 

posted @ 2020-04-04 14:32  sumoier  阅读(169)  评论(0编辑  收藏  举报