zoukankan      html  css  js  c++  java
  • 背包

    背包

    blog背包九讲

    n件物品,容量为V的背包,w价值,v体积

    $ dp[i][j]=dp[i-1][j-k*v[i]]+k*w[i] $

    01背包

    每个物品只有一件 k=1

    memset(f,0xcf,sizoef(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++)
        for(int j=V;j>=v[i];j--)//注意是倒序 
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    

    完全背包

    每种物品使用无限次

    for(i=1;i<=n;i++)
        for(j=v[i];j<=V;j++)//物品用了可以再用,不用考虑i;注意,这里是“顺序 ”
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    cout<<f[V]<<endl;
    

    多重背包

    i 种物品有 n[ i ]

    O(V*∑logn[i])

    摆花

    记忆化搜索还是很好理解,虽然复杂度难以描述

    #include <cstdio>
    #include <iostream>
    using namespace std;
    int dp[105][105],n,m;
    const int mod=1000007;
    int a[105];
    int dfs(int i,int j) {
        if(j>m) return 0;
        if(j==m) return 1;
        if(i>n) return 0;
        if(dp[i][j]) return dp[i][j];
        int ans=0;
        for(int k=0;k<=a[i];k++)
            (ans+=dfs(i+1,j+k))%=mod;
        dp[i][j]=ans;
        return ans;    
    }
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        printf("%d
    ",dfs(1,0));
        return 0;
    }
    

    然后我们考虑dp

    推出三重for的式子

    #include <cstdio>
    #include <iostream>
    using namespace std;
    int dp[105][105],n,m;
    const int mod=1000007;
    int a[105];
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        dp[0][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=0;j<=m;j++)
                for(int k=0;k<=min(j,a[i]);k++)
                    (dp[i][j]+=dp[i-1][j-k])%=mod;
        printf("%d
    ",dp[n][m]);
        return 0;
    }
    

    这其实就是多重背包最原始的样子

    然后发现 i 只和 i-1 有关,我们搞个滚动数组

    dp[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++){
            dp[i&1][j]=0;//莫忘初始化
            for(int k=0;k<=min(j,a[i]);k++)
                (dp[i&1][j]+=dp[i&1^1][j-k])%=mod;
        }
    printf("%d
    ",dp[n&1][m]);
    

    然后,,,这不就是个01背包吗

    直接压成01背包

    f[0] = 1;
    for(int i=1; i<=n; i++)
        for(int j=m; j>=0; j--) 
            for(int k=1; k<=min(a[i], j); k++)
                f[j] = (f[j] + f[j-k])%mod;
    cout<<f[m]<<endl;
    

    二进制拆分

    做法:把每一个物品根据2的多少次方拆分,因为任何数都可以转化为二进制数

    核心思想:把每一个物品拆成很多个,分别计算价值和所需时间,再转化为01背包求解

    #include <iostream>
    #include <cstdio>
    using namespace std;
    inline int read() {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f; 
    }
    const int N=100005;
    int n,h1,h2,t1,t2,m;
    int v[N],w[N],c[N],f[1005];
    int cost[N],val[N],tp;//开大
    void pre() {
        for(int i=1;i<=n;i++) {
            int t=1;
            while(c[i]) {
                cost[++tp]=t*v[i];
                val[tp]=t*w[i];
                c[i]-=t;
                t*=2;
                if(c[i]<t) {//如果剩下的不能再拆,就直接放在一起
                    cost[++tp]=c[i]*v[i];
                    val[tp]=c[i]*w[i];
                    break;
                }
            }
        }
    }
    int main() {
        scanf("%d:%d%d:%d",&h1,&t1,&h2,&t2);
        m=(h2*60+t2)-(h1*60+t1);
        n=read();
        for(int i=1;i<=n;i++) {
            v[i]=read();w[i]=read();c[i]=read();
            if(!c[i]) c[i]=99999;
        }
        pre();
        for(int i=1;i<=tp;i++)
            for(int j=m;j>=cost[i];j--)
                f[j]=max(f[j],f[j-cost[i]]+val[i]);
        printf("%d
    ",f[m]);
        return 0;   
    }
    

    单调队列优化 O(n*V)

    https://www.cnblogs.com/-guz/p/9866118.html

    蓝书P341 讲的很好

    image-20200718204945938

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    inline int read() {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f; 
    }
    const int N=1e5+10;
    int n,m,f[N],q[N];
    int w[N],v[N],c[N];
    int calc(int i,int d,int k) {
        return f[d+k*v[i]]-k*w[i];
    }
    int main() {
        memset(f,0xcf,sizeof(f));
        f[0]=0;
        n=read();m=read();
        for(int i=1;i<=n;i++) {
            w[i]=read();v[i]=read();c[i]=read();
            for(int d=0;d<v[i];d++) {
                int l=1,r=0,maxp=(m-d)/v[i];
                for(int k=maxp-1;k>=max(maxp-c[i],0);k--) {
                    while(l<=r&&calc(i,d,q[r])<=calc(i,d,k)) r--;
                    q[++r]=k;
                }
                for(int p=maxp;p>=0;p--) {
                    while(l<=r&&q[l]>p-1) l++;
                    if(l<=r) f[d+p*v[i]]=max(calc(i,d,q[l])+p*w[i],f[d+p*v[i]]);
                    if(p-c[i]-1>=0) {
                        while(l<=r&&calc(i,d,q[r])<=calc(i,d,p-c[i]-1)) r--;
                        q[++r]=p-c[i]-1;
                    }
                }
            }
        }
        int ans=0;
        for(int i=1;i<=m;i++)
            ans=max(ans,f[i]);
        printf("%d
    ",ans);
        return 0;
    }
    

    混合背包

    有一个具有n种货币的货币系统, 每种货币的面值为val[i]. 现在小杰手上拿着num[1],num[2],…num[n]个第1种,第2种…第n种货币
    去买价值为T(T<=20000)的商品, 他给售货员总价值>=T的货币,然后售货员(可能,如果小杰给的钱>T,那肯定找钱)找钱给他.
    售货员每次总是用最少的硬币去找钱给小杰. 现在的问题是: 小杰买价值T的商品时, 他给售货员的硬币数目+售货员找他的硬币数目最少等于多少?

    第一行先输入n和c,n代表硬币的种类数,c代表顾客需要付的款数,
    第二行是顾客n种硬币的面值
    第三行是顾客相应的n种硬币的个数

    顾客和店员的区别:
    具有的硬币的种类数都是n种,不过顾客的硬币数是有限的(输入的相应硬币数目),店员的硬币数目是无限的
    现在顾客需要买c元的东西,只能用这些硬币付款,且顾客手中不同种类的硬币的数目是有限的,所以现在顾客付款有两种情况:
    1.店员不需要找钱 顾客手中的硬币种类和个数刚好可以组成c元
    2.店员需要找钱,顾客手中硬币种类和个数不可以恰好组成c元,所以顾客只能给T元,店员给顾客找钱c-T元
    现在要 求经过店员和顾客手中的硬币的最小数目

    解题思路;
    主要的方向就是对店员进行完全背包从操作,对顾客进行多重背包的操作,然后最后遍历两个dp数组,找到最小硬币数目(t=min(t,dp2[i]+dp1[i-c]))
    需要注意的地方:
    dp数组初始化
    因为是要能付款,而不是求硬币数目最少,所以是"恰恰好装满背包“
    所以两个数组除了dp[0]以外,都要赋无穷大

    #include<bits/stdc++.h>
    using namespace std;
    int n,V,v[10005],w[10005],s[10005],f[21000],h=0;
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
        x*=f;
    }
    int main()
    {
        read(n);read(V);
        for(int i=1;i<=n;i++){
            int x,y,z,t;
            read(x),read(y),read(z);
            if(z<0)               
                c[++h]=x,w[h]=y,s[h]=1;
            else if(z==0)
                c[++h]=x,w[h]=y,s[h]=0;
            else if (z > 0) {
                t=1;
                while(t<=z){
                    c[++h]=t*x;
                    w[h]=t*y;
                    s[h]=1;
                    z-=t;
                    t<<=1;
                }
                c[++h]=z*x;
                w[h]=z*y;
                s[h]=1;
            }
        }
        for(int i=1;i<=h;i++){//注意这里是h,不是n     
            if(s[i]==0){ //完全         
                for(int j=c[i];j<=v;j++)
                f[j]=max(f[j],f[j-c[i]]+w[i]);
            }
            else if(s[i]==-1||s[i]==1){//01        
                for(int j=v;j>=c[i];j--)
                f[j]=max(f[j],f[j-c[i]]+w[i]);
            }
        }
        cout<<f[V]<<endl;
        return 0;
     } 
    

    分组背包

    https://www.acwing.com/problem/content/9/

    #include <iostream>
    #include <cstdio>
    using namespace std;
    const int N=105;
    int f[N],n,m,s[N],v[N][N],w[N][N];
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) {
            scanf("%d",&s[i]);
            for(int j=1;j<=s[i];j++)
                scanf("%d%d",&v[i][j],&w[i][j]);
        }
        for(int i=1;i<=n;i++) 
            for(int j=m;j>=0;j--)
                for(int k=1;k<=s[i];k++)
                    if(j>=v[i][k])
                        f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
        printf("%d
    ",f[m]);
        return 0;
    }
    

    有依赖的背包

    金明的预算方案

    #include <iostream>
    #include <cstdio>
    using namespace std;
    const int N=5000010;
    int n,m;
    struct node{
        int v,w,q;
    }a[N];
    int h[N],f[N];
    int main() {
        scanf("%d%d",&m,&n);
        for(int i=1;i<=n;i++) 
            scanf("%d%d%d",&a[i].v,&a[i].w,&a[i].q),a[i].w*=a[i].v;
        for(int i=1;i<=n;i++) {
            if(!a[i].q) {
                for(int j=1;j<a[i].v;j++) h[j]=0;
                for(int j=a[i].v;j<=m;j++)
                    h[j]=f[j-a[i].v]+a[i].w;
                for(int j=1;j<=n;j++)
                    if(a[j].q==i) {
                        for(int k=m;k>=a[i].v+a[j].v;k--)
                            h[k]=max(h[k],h[k-a[j].v]+a[j].w);
                    }
                for(int j=a[i].v;j<=m;j++)
                    f[j]=max(f[j],h[j]);
            }
        }
        printf("%d
    ",f[m]);
        return 0;
    }
    

    https://www.acwing.com/problem/content/description/10/

    #include <iostream>
    #include <cstdio>
    #include <vector>
    using namespace std;
    const int N=105;
    int f[N][N],n,m,root,v[N],w[N];
    vector<int>g[N];
    void dfs(int x) {
        for(int i=v[x];i<=m;i++) 
            f[x][i]=w[x];//点x必须选
        for(int i=0;i<g[x].size();i++) {
            int y=g[x][i];
            dfs(y);
            for(int j=m;j>=v[x];j--)
                for(int k=0;k<=j-v[x];k++)//子树的体积
                    f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);
        }
    }
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1,fa;i<=n;i++) {
            scanf("%d%d%d",&v[i],&w[i],&fa);
            if(fa==-1) root=i;
            else g[fa].push_back(i);
        }
        dfs(root);
        printf("%d
    ",f[root][m]);
        return 0;
    }
    

    https://oi-wiki.org/dp/knapsack/

    题目:

    疯狂的火神

    这很显然是个01背包,体积为 c[] , 价值 $a[]-b[]*x $,我们怎么搞这个时间呢?

    设$ i$ 优于 (j) 先选,已经用时(time),那么 $ a[i]-b[i]*time+a[j]-b[j]*time+>a[j]-b[j]*time+a[i]-b[i]*(time+c[j]) $

    化简得$ c[i]*b[j] < b[i]*c[j] $

    #include <bits/stdc++.h>
    using namespace std;
    const int maxt=2147483647;
    inline int read()
    {
        int x=0,k=1; char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
        while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
        return x*k;
    }
    int f[3010],ans=0;
    int t,n,m;
    struct node{
    	int a,b,c;
    }x[1005];
    bool cmp(node x,node y){
    	return x.c*y.b<x.b*y.c;
    }
    int main(){
    	t=read();
    	while(t--){
    		memset(f,0,sizeof(f));
    		ans=-maxt;
    		n=read(),m=read();
    		for(int i=1;i<=n;i++)
    		x[i].a=read(),x[i].b=read(),x[i].c=read();
    		sort(x+1,x+1+n,cmp);
    		for(int i=1;i<=n;i++)
    			for(int j=m;j>=x[i].c;j--){ 
    				f[j]=max(f[j],f[j-x[i].c]+x[i].a-x[i].b*j);
    				ans=max(ans,f[j]);
    			}
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    poj1015

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    using namespace std;
    
    int n,m,cas=1,fix,p,d,S,A,P,D;
    int dp[25][805],sub[205],add[205];
    vector<int> path[25][805];
    
    int main(){
        while(~scanf("%d%d",&n,&m)&&n){
            memset(dp,-1,sizeof(dp));
            for(int i=1;i<=m;++i)
                for(int j=0;j<805;++j)
                    path[i][j].clear();
            fix=20*m;
            dp[0][fix]=0;
            for(int i=1;i<=n;++i){
                scanf("%d%d",&p,&d);
                sub[i]=p-d;
                add[i]=p+d;
            }
            for(int i=1;i<=n;++i)
                for(int j=m-1;j>=0;--j)
                    for(int k=0;k<=2*fix;++k)
                        if(dp[j][k]>=0)
                            if(dp[j][k]+add[i]>dp[j+1][k+sub[i]]){
                                dp[j+1][k+sub[i]]=dp[j][k]+add[i];
                                path[j+1][k+sub[i]]=path[j][k];
                                path[j+1][k+sub[i]].push_back(i);
                            }
            int kk;
            for(kk=0;kk<=fix;++kk)
                if(dp[m][fix+kk]>=0||dp[m][fix-kk]>=0)
                    break;
            S=dp[m][fix+kk]>dp[m][fix-kk]?fix+kk:fix-kk;
            A=dp[m][S];
            P=(A+(S-fix))/2,D=(A-(S-fix))/2;
            printf("Jury #%d
    ",cas++);
            printf("Best jury has value %d for prosecution and value %d for defence:
    ",P,D);
            for(int i=0;i<m;++i)
                printf(" %d",path[m][S][i]);
            printf("
    
    ");
        }
        return 0;
    }
    
  • 相关阅读:
    转载一篇文章 python程序员经常犯的10个错误
    外部表与partition
    grpc 入门(二)-- 服务接口类型
    用例图简介(转)
    UML类图(Class Diagram)中类与类之间的关系及表示方式(转)
    快速搭建fabric-v1.1.0的chaincode开发环境
    ubuntu networking 与 network-manager
    [转]bashrc与profile区别
    超矩链--基于矩阵的分布式账本
    adb 在windows7中的使用
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13501320.html
Copyright © 2011-2022 走看看