背包 基础题集

HDU 1171  杭电分设备(多重背包)

题意:给一组物品,要求分给A,B 2个人,要求A分到的价值总和不小于B,输出A,B分到的价值总和。

题解:多重背包问题:思路比较简单,设sum为物品的总价值

  方法1:直接打背包价值的表F[V](装满背包初始化为-INF),从v=sum/2往右找,满足F[v]>0的即可; 

  方法2:设背包容量为V=sum/2,直接找F[V]即可;

反思:当价值和容量是同一种的时候,基本都有2种方法,初始化为-INF,找满足条件下的因变量下对于的自变量x(什么样容量的背包可以被装满),

   或初始化为0(这个背包最多可以装下的价值),找因变量;(把背包问题看成函数求最优解,x为定义域(容量),y为值域(价值总和等));

方法1代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int maxn=2e6+5;
const int INF=0x3f3f3f3f;
int f[maxn], c[maxn], w[maxn];
int main()
{
    //freopen("in.txt", "r", stdin);
    int n;
    while (cin>>n, n>=0)
    {
        int all_n=1, sum=0;
        while(n--)
        {
            int val,num,k; cin>>val>>num;
            sum=sum+val*num;
            for(k=1; k<num; k<<=1){
                w[all_n++]=k*val;
                num=num-k;
            }
            if(num)
                w[all_n++]=num*val;
        }
        for(int v=0; v<=sum; v++) f[v]=-INF;
        f[0]=0;
        for(int i=1; i<all_n; i++)
            for(int v=sum; v>=w[i]; v--)
                f[v]=max(f[v], f[v-w[i]]+w[i]);
        int tmp;
        if(sum%2) tmp=sum+1;
        else tmp=sum;

        int ans=0;
        for(int v=tmp/2; v>=0; v++)
            if(f[v]>0){
                ans=v; break;
            }
        cout<<ans<<" "<<sum-ans<<endl;

    }
    return 0;
}
View Code

方法2代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int maxn=2e6+5;
const int INF=0x3f3f3f3f;
int f[maxn], c[maxn], w[maxn];
int main()
{
    //freopen("in.txt", "r", stdin);
    int n;
    while (cin>>n, n>=0)
    {
        int all_n=1, sum=0;
        while(n--)
        {
            int val,num,k; cin>>val>>num;
            sum=sum+val*num;
            for(k=1; k<num; k<<=1){
                w[all_n++]=k*val;
                num=num-k;
            }
            if(num)
                w[all_n++]=num*val;
        }
        memset(f, 0, sizeof(f));
        for(int i=1; i<all_n; i++)
            for(int v=sum/2; v>=w[i]; v--)
                f[v]=max(f[v], f[v-w[i]]+w[i]);
        printf("%d %d\n", sum-f[sum/2], f[sum/2]);
    }
    return 0;
}
View Code

 

 

HDU 2844 硬币(多重背包)

题意:给你物品,求这些物品可以组成多少种的价值(背包容量有限制的);

题解:和上题很相似有2种方法,但是直接初始化为0,再循环打表,循环遍历,输入到set中,通过set去重,再输出答案,(我写的代码是TLE了,有博客上是990ms过了)

  明显初始为-INF,求什么容量的背包可以被装满要简单不少,这样就不用set去重了(680ms过);

反思:有思路的时候不要急于去实现代码,可以再多思考那么一点,是否可以优化,避免不必要的运算;

  还有个问题,通过这题发现,能不用fill就不要用fill,这个函数并不适合于ACM,更适合于做做工程;

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int INF=0x3f3f3f3f;
const int maxn=2e6+5;
int f[maxn], a[105], b[105];

int main()
{
    //freopen("in.txt", "r", stdin);
    int n, V;
    while(cin>>n>>V, n+V)
    {
        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=0; i<=V; i++)
            f[i]=-INF;         //688msAC
        //fill(f, f+V, -INF);  //超时1000ms,TLE
        //我tm是服了...查了一下午
        f[0]=0;
        for(int i=1; i<=n; i++)
        {
            int t=b[i], k=1;
            while(k<t){
                for(int v=V; v>=k*a[i]; v--)
                    f[v]=max(f[v], f[v-k*a[i]]+k*a[i]);
                t=t-k;
                k<<=1;
            }
            if(t)
                for(int v=V; v>=t*a[i]; v--)
                    f[v]=max(f[v], f[v-t*a[i]]+t*a[i]);
        }
        int ans=0;
        for(int v=1; v<=V; v++){
            //printf("f[%d]=%d\n",v,f[v]);
            if(f[v]>0) ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}
View Code

 

 

HDU 3033 (有限制的分组背包问题)

题意:给出物品(有分组的),从每组选一个(背包容量有限),求最大的权值和;

题解:分组背包问题,不过有个限制(和学的背包九讲不同),每组必须一个,又有点二维背包的意思(有一个维度必须装满),二维数组实现,直接想状态转移还是不难的;

    f[i][v]=max(max(f[i][v], f[i][v-c[j]]+w[j]), f[i-1][v-c[j]]+w[j]); ( i是指组 )

   方程不难想,但是转移的时候这个是有顺序的,优先考虑从这一层转移来的,再考虑从上一层转移来的(不然会有重复运算),直接max(状态1, 状态2, 状态3)也行;

反思:这些题目不会写的时候 要回归dp问题,找状态和决策,再找状态转移方程

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int INF=0x3f3f3f3f, maxn=105;
int c[maxn], kind[maxn], w[maxn], f[maxn][10005];

int main()
{
    //freopen("in.txt", "r", stdin);
    int n,m,k;
    while(cin>>n>>m>>k)
    {
        fill(f[0], f[0]+maxn*10005, -INF);
        memset(f[0], 0, sizeof(f[0]));
        for(int i=1; i<=n; i++)
            cin>>kind[i]>>c[i]>>w[i];

        for(int i=1; i<=k; i++)
            for(int j=1; j<=n; j++)
                if(kind[j]==i)
                    for(int v=m; v>=c[j]; v--)
                        f[i][v]=max(max(f[i][v], f[i][v-c[j]]+w[j]), f[i-1][v-c[j]]+w[j]);
        if(f[k][m]<0)
            cout<<"Impossible"<<endl;
        else
            cout<<f[k][m]<<endl;
    }
    return 0;
}
View Code

 

 

 HDU 3732 (01背包要优化)

题意:给了n个物品,n很大,求出满足背包容量v的最大权值和;

题解:一眼扫去,01背包,敲代码,TLE;观察数据发现,n*n的(n有点大)复杂度肯定超时间,

  题目所给的c和w比较小,但是n很大,可以尝试化为多重背包写,运用2进制的思想压缩物品来优化时间复杂度;

反思:看到题目给的数据就应该要意识到大概需要什么样的复杂度;

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int maxn=1e4+5;
int g[15][15], f[maxn];
int main()
{
    //freopen("in.txt", "r", stdin);
    int n,V;
    while (cin>>n>>V)
    {
        memset(g, 0, sizeof(g));
        while(n--){
            getchar();
            char s[15]; scanf("%s",s);
            int w,c; scanf("%d%d",&w, &c);
            g[w][c]++;
        }

        memset(f, 0, sizeof(f));
        for(int w=0; w<=10; w++)
            for(int c=0; c<=10; c++)
            {
                int s=g[w][c];
                int t=1;
                while(t<s){
                    for(int v=V; v>=t*c; v--)
                        f[v]=max(f[v], f[v-t*c]+t*w);
                    s=s-t;
                    t=2*t;
                }
                if(s!=0)
                    for(int v=V; v>=s*c; v--)
                        f[v]=max(f[v], f[v-s*c]+s*w);
            }
        cout<<f[V]<<endl;
    }
    return 0;
}
View Code

 

 

HDU 1955(抢银行的概率)

题意:每一个银行都有一个被抓的概率和能获得的钱,求在可以可以忍受的概率下能抢劫到的最多的钱;

题解:01背包,但是有了个浮点数, 需要转化,有2个问题:1.背包容量的概率,浮点数,需要转化背包的容量和价值;2.概率的计算问题,被抓的概率算不被抓的概率方便一些,注意是乘法;

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

double f[1000007];
struct BANK{
    int c;
    double p;
}bank[107];

int main()
{
    //freopen("in.txt", "r", stdin);
    int T; cin>>T;
    while(T--)
    {
        double val; cin>>val;
        val=1-val;   //逃跑的概率
        int n; cin>>n;
        int V=0;
        for(int i=1; i<=n; i++)
        {
            cin>>bank[i].c>>bank[i].p;
            V+=bank[i].c;
        }

        memset(f, 0, sizeof(f));
        f[0]=1.0;
        int ans=0;
        for(int i=1; i<=n; i++)
            for(int v=V; v>=bank[i].c; v--)
            {
                //f[v]=max(f[v], f[v-bank[i].c]+bank[i].p); 错解,数学是真垃圾呀!
                f[v]=max(f[v], f[v-bank[i].c]*(1-bank[i].p));
                /*if(i==n&&f[v]>val){
                    ans=v; break;
                }*/

            }

        for(int v=V; v>=0; v--)
        {
            if(f[v]>val){
                ans=v; break;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}
View Code

 

 

HDU 3496 买电影票的

题意:每次只能买m张电影票,求用V的钱,能买到的电影的最大价值;

题解:这题是个二维背包,而且是有一个维度需要装满,初始化为-INF打个表,再找一下v1=m,v2 <V的最大价值即可,我看有些博客初始化为-1,单独判断==-1和初始化为-INF的思路都一样,判断是否由合法的状态转移来的。

 
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn=107;
const int INF=0x3f3f3f3f; 
int f[maxn][1007], c2[maxn], w[maxn]; 

int main()
{
    //freopen("in.txt", "r", stdin);
    int T; cin>>T;
    while(T--)
    {
        for(int i=0; i<maxn; i++)
            for(int j=0; j<1007; j++)
                f[i][j]=-INF;  
        int n,V1,V2; cin>>n>>V1>>V2;
        f[0][0]=0; 
        for(int i=1; i<=n; i++)
            cin>>c2[i]>>w[i];
        for(int i=1; i<=n; i++)
            for(int v1=V1; v1>=1; v1--)
                for(int v2=V2; v2>=c2[i]; v2--)
                        f[v1][v2]=max(f[v1][v2], f[v1-1][v2-c2[i]]+w[i]);
        int ans=0; 
        for(int i=0; i<=V2; i++)
            ans=max(ans, f[V1][i]);
        cout<<ans<<endl;
    }
    return 0;
}
View Code

 初始化为-1的情况:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn=107; 
int f[maxn][1007], c2[maxn], w[maxn]; 

int main()
{
    //freopen("in.txt", "r", stdin);
    int T; cin>>T;
    while(T--)
    {
        memset(f, -1, sizeof(f));    //-1方便后面的判断,题目有个条件:只卖m部(装满背包),状态转移要注意 
        int n,V1,V2; cin>>n>>V1>>V2;
        f[0][0]=0; 
        for(int i=1; i<=n; i++)
            cin>>c2[i]>>w[i];
        for(int i=1; i<=n; i++)
            for(int v1=V1; v1>=1; v1--)
                for(int v2=V2; v2>=c2[i]; v2--)
                    if(f[v1-1][v2-c2[i]]!=-1)    //这个判定其实过于严格,所以后面要找一下最大值 
                        f[v1][v2]=max(f[v1][v2], f[v1-1][v2-c2[i]]+w[i]);
        int ans=0;  //维度1要求装满,维度2不要求必须装满,我们是当作转满算的,要找一下最大值 
        for(int i=0; i<=V2; i++)
            ans=max(ans, f[V1][i]);
        cout<<ans<<endl;
    }
    return 0;
}
View Code

初始话为-INF,第一行为0,不需要遍历找ans;

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int MAXN = 1e7;
const int INF=0x3f3f3f3f;
int dp[111][1111];

int main() {
    int t;
    cin >> t;
    while (t--) {
        //memset(dp, -1, sizeof(dp));
        int n, m, l;
        cin >> n >> m >> l;
        vector<int> w(n), v(n);
        for (int i = 0; i < n; ++i) {
            cin >> w[i] >> v[i];
        }

        for(int i=0; i<111; i++)
            for(int j=0; j<1111; j++)
                dp[i][j]=-INF;

        for (int i = 0; i <= l; ++i) {
            dp[0][i] = 0;
        }
        for (int i = 0; i < n; ++i)
            for (int j = m; j >= 1; --j)
                for (int k = l; k >= w[i]; --k)
                        dp[j][k] = max(dp[j][k], dp[j - 1][k - w[i]] + v[i]);



       cout << (dp[m][l] <0 ? 0 : dp[m][l]) << endl;
    }

    return 0;
}
View Code

 

 

HDU 2546 饭卡食堂买菜

题解:有特殊情况的01背包,

读题没怎么弄明白,这个买饭是一次买一个菜,好像和现实不太一样??
因为剩下的五块可以买任意价值的物品,所以在进行递推的过程中先将总额减去5块,进行n-1个菜的递推
(菜价从低到高排好序,排序是为了最后的5元能取得最大值)。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=1007; 
int f[maxn], c[maxn];

int main()
{
    int n; 
    while(cin>>n, n)
    {
        memset(c, 0, sizeof(c));
        memset(f, 0, sizeof(f));
        for(int i=1; i<=n; i++) 
            cin>>c[i];
        int m; cin>>m;
        
        if(m<5){
            cout<<m<<endl;
            continue;
        }
        sort(c+1, c+1+n);  
        for(int i=1; i<=n-1; i++)
            for(int v=m-5; v>=c[i]; v--)
                f[v]=max(f[v], f[v-c[i]]+c[i]); 
        cout<<m-f[m-5]-c[n]<<endl;
    }
    return 0;
}
View Code

 

 

HDU 1864 发票报销

题解:也是有浮点数的情况,是把浮点型放大成int形,再01背包即可,
但是此题的输入很麻烦,这地方我很菜很菜搞了好久,(这种题目一定好好琢磨样例,那些输入是会有问题的)要加强,
有%c时,注意吸收键盘缓冲区,读题的时候也要注意所给的输入,什么是合法的什么是不合法,输入都会有那些情况;

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int maxn=3e6+7;
int w[37], f[maxn];
int main()
{
    //freopen("in.txt", "r", stdin);
    double Q; int n; 
    while(cin>>Q>>n, n)
    {
        int V=Q*100;     
        int all_n=0;      
        while(n--)
        {
            int m; cin>>m;
            char ch; double data; 
            int sum=0, va=0, vb=0, vc=0, flag=1;
            while(m--)
            { 
                getchar();
                scanf("%c:%lf", &ch, &data);
                data=data*100;
                if(flag)
                {
                    if(ch=='A'&&va+data<=60000)
                        va=va+data;
                    else if(ch=='B'&&vb+data<=60000)
                        vb=vb+data;
                    else if(ch=='C'&&vc+data<=60000)
                        vc=vc+data;    
                    else
                        flag=0;    
                }
            }
            sum=va+vb+vc;
            if(flag&&sum<=100000)
                w[all_n++]=sum; 
        }
        memset(f, 0, sizeof(f));
        for(int i=0; i<all_n; i++)
            for(int v=V; v>=w[i]; v--)
                f[v]=max(f[v], f[v-w[i]]+w[i]); 
        double ans=f[V]/100.0;
        printf("%.2f\n", ans); 
     }
    return 0;
}
View Code

 

posted @ 2019-04-10 22:01  N_Yokel  阅读(267)  评论(0编辑  收藏  举报