3.17~3.28背包问题

版权声明: https://blog.csdn.net/zjh_2017/article/details/79726097

01背包

题库P5#416采药
i循环为阶段,即当前考虑第i个物品取不取
j循环为状态,即当前背包容量还剩j
f[i][j]表示已经考虑了前i个物品,背包已装了容量为j的物品时的最大价值
Q:这里j为什么是倒序循环?
A:用反证法——如果j正序循环,那么当更新f[j]时会发现f[j-t[i]]可能已经被更新过。而如果f[j-t[i]]已被更新过的话说明第i个物品已经取了1次,此时再要更新f[j]的话会导致第i个物品又取了1次,这就与01背包每个物品只取1次相违背。因此需要倒序循环。

#include<bits/stdc++.h>
using namespace std;
long long T,m,t[110],v[110],f[1010];
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    T=read();
    m=read();
    for (int i=1;i<=m;++i)
    {
        t[i]=read();
        v[i]=read();
    }
    for (int i=1;i<=m;++i)
    for (int j=T;j>=t[i];--j)
    f[j]=max(f[j],f[j-t[i]]+v[i]);
    printf("%lld\n",f[T]);
    return 0;
}

完全背包

题库P5#418完全背包模版
同样地,i为阶段,j为状态,f[i][j]的意义也一样
但这里,j是正序循环。这是因为一个物品可以取多次,f[j]可以在f[j-t[i]]被更新的基础上再次被更新,这样就保证了第i个物品可以取多次。

#include<bits/stdc++.h>
using namespace std;
long long m,n,v[110],w[110],f[310];
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    m=read();
    n=read();
    for (int i=1;i<=n;++i)
    {
        w[i]=read();
        v[i]=read();
    }
    for (int i=1;i<=n;++i)
    for (int j=w[i];j<=m;++j)
    f[j]=max(f[j],f[j-w[i]]+v[i]);
    printf("max=%lld\n",f[m]);
    return 0;
}

多重背包

题库P5#420逃亡的准备
多重背包最终目的是把它转换成01背包问题。那它是怎么做到的呢?
这里采用二进制拆分法——一个物品有mm个,那么把mm拆成 2 0 + 2 1 + 2 2 + + m m ,mm’值为 m m 2 0 2 1 2 2 ,减到不能再减为止,剩下的值就是它(不为负数)。那么我们就可以用这里的数凑成所有<=mm的数。因此这就转换成了01背包问题。

#include<bits/stdc++.h>
using namespace std;
long long n,v,mm,ww,ss,w[10000010],s[10000010],tot,f[5010];
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    n=read();
    v=read();
    for (int i=1;i<=n;++i)
    {
        mm=read();
        ww=read();
        ss=read();
        int j=1;
        while (1)
        {
            if (mm>j)
            {
                tot++;
                mm-=j;
                w[tot]=ww*j;
                s[tot]=ss*j;
            }
            else
            {
                tot++;
                w[tot]=ww*mm;
                s[tot]=ss*mm;
                break;
            }
            j*=2;
        }
    }
    for (int i=1;i<=tot;++i)
    for (int j=v;j>=w[i];--j)
    f[j]=max(f[j],f[j-w[i]]+s[i]);
    printf("%lld\n",f[v]);
    return 0;
}

混合三种背包

暂时没有题目
友情链接:https://blog.csdn.net/qq_39670434/article/details/79475683

二维费用的背包

暂时没有题目
友情链接:https://blog.csdn.net/qq_39670434/article/details/79476427

分组背包

题库P5#422竞赛真理
因为每一组的物品间有冲突,每一组里最多只能取一个物品,那么在每一组里面采取01背包的思想,在每一组里考虑该物品取不取。

#include<bits/stdc++.h>
using namespace std;
long long n,t,w1[50],w2[50],t1[50],t2[50],f[1080010];
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    n=read();
    t=read();
    for (int i=1;i<=n;++i)
    {
        w1[i]=read();
        t1[i]=read();
        w2[i]=read();
        t2[i]=read();
    }
    for (int i=1;i<=n;++i)
    for (int j=t;j>=min(t1[i],t2[i]);--j)
    {
        if (j>=t1[i]) f[j]=max(f[j],f[j-t1[i]]+w1[i]);
        if (j>=t2[i]) f[j]=max(f[j],f[j-t2[i]]+w2[i]);
    }
    printf("%lld\n",f[t]);
    return 0;
}

有依赖的背包问题

题库P5#423金明的预算方案
有依赖的背包是加强版的分组背包问题,只不过中间加了一个特判,一组物品中可能可以取多个而已。因为是取了主件才可以取附件,因此一定要加这个if判断。而可以省略else的原因是——在if语句里的第一句话已经取了一个max,如果不取主件,那么f[j]还是f[j];如果要取主件,f[j]是会被更新的。

#include<bits/stdc++.h>
using namespace std;
long long n,m,vv,pp,qq,v[100][5],p[100][5],f[32010];
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    n=read();
    m=read();
    for (int i=1;i<=m;++i)
    {
        vv=read();
        pp=read();
        qq=read();
        if (qq==0)
        {
            v[i][0]=vv;
            p[i][0]=pp;
        }
        else
        {
            if (v[qq][1]==0)
            {
                v[qq][1]=vv;
                p[qq][1]=pp;
            }
            else
            {
                v[qq][2]=vv;
                p[qq][2]=pp;
            }
        }
    }
    for (int i=1;i<=m;++i)
    for (int j=n;j>=0;--j)
    if (j>=v[i][0])
    {
        f[j]=max(f[j],f[j-v[i][0]]+v[i][0]*p[i][0]);
        if (j>=v[i][0]+v[i][1])
        f[j]=max(f[j],f[j-v[i][0]-v[i][1]]+v[i][0]*p[i][0]+v[i][1]*p[i][1]);
        if (j>=v[i][0]+v[i][2])
        f[j]=max(f[j],f[j-v[i][0]-v[i][2]]+v[i][0]*p[i][0]+v[i][2]*p[i][2]);
        if (j>=v[i][0]+v[i][1]+v[i][2])
        f[j]=max(f[j],f[j-v[i][0]-v[i][1]-v[i][2]]+v[i][0]*p[i][0]+v[i][1]*p[i][1]+v[i][2]*p[i][2]);
    }
    printf("%lld\n",f[n]);
    return 0;
}

泛化物品背包

暂时没有题目
友情链接:https://blog.csdn.net/qq_39670434/article/details/79483102

背包问题的变化

呵,变化可真多!

T1

题库P5#417集合求和
这是01背包的求方案总数,把max改成求和

#include<bits/stdc++.h>
using namespace std;
long long n,sum,a[50],f[1000010]={1}; 
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    n=read();
    sum=(n+1)*n/2;
    if (sum%2==1)
    {
        printf("0\n");
        return 0;
    }
    for (int i=1;i<=n;++i)
    a[i]=i;
    for (int i=1;i<=n;++i)
    for (int j=sum/2;j>=a[i];--j)
    f[j]=f[j]+f[j-a[i]];
    printf("%lld\n",f[sum/2]/2);
    return 0;
}

T2

题库P5#419质数和分解
这是完全背包的求方案总数,同样地,求和

#include<bits/stdc++.h>
using namespace std;
long long n,a[5010],temp,f[1000010]={1}; 
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
bool panduan(int t)
{
    for (int i=2;i<=sqrt(t);++i)
    if (t%i==0) return false;
    return true;
}
int main()
{
    n=read();
    for (int i=2;i<=n;++i)
    if (panduan(i)) a[++temp]=i;
    for (int i=1;i<=temp;++i)
    for (int j=a[i];j<=n;++j)
    f[j]=f[j]+f[j-a[i]];
    printf("%lld\n",f[n]);
    return 0;
}

T3

题库P5#421潜水员的规划
因为它要计算为了完成他的工作需要的气缸的重量的最低值,首先要保证能完成他的工作,这就要求f[][]的下标>=T和A,所以i和j循环枚举的时候要适当扩大范围,然后把max改成min

#include<bits/stdc++.h>
using namespace std;
long long T,A,n,t[1010],a[1010],w[1010],f[1000][1000],ans;
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    T=read();
    A=read();
    n=read();
    for (int i=1;i<=n;++i)
    {
        t[i]=read();
        a[i]=read();
        w[i]=read();
    }
    memset(f,10,sizeof(f));
    ans=f[0][0];
    f[0][0]=0;
    for (int i=1;i<=n;++i)
    for (int j=T+100;j>=t[i];--j)
    for (int k=A+100;k>=a[i];--k)
    f[j][k]=min(f[j][k],f[j-t[i]][k-a[i]]+w[i]);
    for (int i=T;i<=T+100;++i)
    for (int j=A;j<=A+100;++j)
    ans=min(ans,f[i][j]);
    printf("%lld\n",ans);
    return 0;
}

T4

题库P5#425背包的第k优解
这是求前k优解之和,大佬采用的都是用两个标记外加一个数组,逐层更新。这里f[j][k]表示的就是在背包体积为j时的第k优解的价值。

#include<bits/stdc++.h>
using namespace std;
long long K,V,n,t[210],v[210],a1,a2,temp,a[100],f[5010][100],ans; 
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
int main()
{
    K=read();
    V=read();
    n=read();
    for (int i=1;i<=n;++i)
    {
        t[i]=read();
        v[i]=read(); 
    }
    memset(f,128,sizeof(f));
    f[0][1]=0;
    for (int i=1;i<=n;++i)
    for (int j=V;j>=t[i];--j)
    {
        a1=1;
        a2=1;
        temp=0;
        while (temp<K)
        {
            if (f[j][a1]>=f[j-t[i]][a2]+v[i])
            {
                a[++temp]=f[j][a1];
                a1++;
            }
            else
            {
                a[++temp]=f[j-t[i]][a2]+v[i];
                a2++;
            }
        }
        for (int k=1;k<=K;++k)
        f[j][k]=a[k];
    }
    for (int i=1;i<=K;++i)
    ans+=f[V][i];
    printf("%lld\n",ans);
    return 0;
}

破锣乐队(还没做过

猜你喜欢

转载自blog.csdn.net/zjh_2017/article/details/79726097