(DP练习记录

背包dp

HDU - 2602   

普通的01背包

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1005;
int w[maxn],c[maxn],f[maxn];
int main()
{
    int i,j,T,N,V;
    scanf("%d",&T);
    while (T--)
    {
        memset(f,0,sizeof(f));
        scanf("%d%d",&N,&V);
        for (i=1;i<=N;i++) scanf("%d",&w[i]); 
        for (i=1;i<=N;i++) scanf("%d",&c[i]); 
        for (i=1;i<=N;i++)
        for (j=V;j>=c[i];j--)
            f[j]=max(f[j],f[j-c[i]]+w[i]);
        printf("%d\n",f[V]);
    }
    return 0;
}
hdu2602

HDU - 2955

题意:有个人想抢银行。有n个银行,抢劫银行i可以得到银行的储蓄金额c[i],并且有w[i]的概率被捕。给出概率p,要求出在被抓概率小于p的情况下,能够得到的最大金额数。  

分析:01背包(恰好装满背包)。把银行储蓄总和当作背包容量V,每个银行的储蓄当作体积c,不被抓住的概率当作价值w。初始化f[0]=1,其余为0。状态转移方程为f[j]=max(f[j],f[j-c[i]]*(1.0-w[i]))。求出的f[j]代表恰好装满容量为j的背包所不被抓住的最大概率。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxv=10005;
const int maxn=105;
double w[maxn],f[maxv];
int c[maxn];
int main()
{
    int i,j,T,N,V;
    double p;
    scanf("%d",&T);
    while (T--)
    {
        V=0;
        memset(f,0,sizeof(f));
        scanf("%lf%d",&p,&N);
        for (i=1;i<=N;i++) 
        {
            scanf("%d%lf",&c[i],&w[i]);  
            V+=c[i];
            //所有银行的总金为容量 
        }
        f[0]=1;
        for (i=1;i<=N;i++)
        for (j=V;j>=c[i];j--)
        {
            f[j]=max(f[j],f[j-c[i]]*(1.0-w[i]));
            //f记录获得j而不被抓住的最大概率值 
        }
        for (i=V;1-f[i]>=p;i--) continue;   
        printf("%d\n",i);
    }
    return 0;
}
hdu2955

POJ - 2184

题意:有N头牛,每头牛i有权值s[i]与f[i]。选出若干头牛,使得它们的s之和MS与t之和MT均不小于0,并且MS+MT最大。求出最大的MS+MT的值。

分析:01背包(并不是二维费用)。将s当作体积,f当作价值做01背包,最后遍历一遍找i+dp[i]的最大值。

注意s,f的范围为(-1000,1000),在dp过程中,将体积加上一个数使它非负。

当s大于0时从大到小循环,否则从小到大循环。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1005;
const int lim=100000;
int s[maxn],f[maxn],dp[200005];
int main()
{
    int i,j,ans=0,N;
    scanf("%d",&N);
    memset(dp,128,sizeof(dp));
    dp[lim]=0;
    for (i=1;i<=N;i++) scanf("%d%d",&s[i],&f[i]); 
    for (i=1;i<=N;i++)
    {
        if (s[i]>=0) 
        {
            for (j=2*lim;j>=s[i];j--)
                dp[j]=max(dp[j],dp[j-s[i]]+f[i]);
        }
        else 
        {
            for (j=0;j<=2*lim+s[i];j++)
                dp[j]=max(dp[j],dp[j-s[i]]+f[i]);
        }
    }
    for (i=lim;i<=2*lim;i++)
        if (dp[i]>=0) ans=max(ans,i+dp[i]-lim); 
    printf("%d\n",ans);
    return 0;
}
poj2184

 POJ - 2923 

题意:有两辆卡车,承重分别为C1,C2。有n件家具,每件家具i有重量w[i]。问最少需要运几趟能把家具运完。

分析:状态压缩+01背包。N<=10,所以可以用二进制表示家具的状态。

首先处理出那些状态能够由这两辆卡车一次运输完。

假设能够一次运输完的家具状态为st[i],将st[i]当作物品的体积,1为物品的价值(运输趟数+1),有状态转移方程dp[st[i]|j]=min(dp[st[i]|j],dp[j]+1)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1050;
int c[12],vis[maxn],st[maxn],dp[maxn];
int main()
{
    int i,j,k,t,T,N,C1,C2,cnt,sum,inf;
    scanf("%d",&T);
    for (t=1;t<=T;t++)
    {
        scanf("%d%d%d",&N,&C1,&C2);
        memset(st,0,sizeof(st));
        memset(dp,127,sizeof(dp));
        inf=dp[0];
        dp[0]=0;
        cnt=0;
        for (i=0;i<N;i++) scanf("%d",&c[i]); 
        for (i=1;i<(1<<N);i++)
        {
            memset(vis,0,sizeof(vis));
            vis[0]=1;
            sum=0;
            for (j=0;j<N;j++)
            {
                if ((i>>j)&1) 
                {
                    sum+=c[j];
                    for (k=C1;k>=c[j];k--) 
                        if (vis[k-c[j]]) vis[k]=1;
                }
            }
            if (sum<=C1+C2)
            {
                for (j=0;j<=C1;j++)
                    if (vis[j] && sum-j<=C2) 
                    {
                        st[++cnt]=i;
                        break;
                    }    
            }
        }
        for (i=1;i<=cnt;i++)
        {
            for (j=(1<<N)-1;j>=0;j--)
                if ((st[i]&j)==0 && dp[j]!=inf) dp[st[i]|j]=min(dp[st[i]|j],dp[j]+1); 
        }
        printf("Scenario #%d:\n%d\n\n",t,dp[(1<<N)-1]);
    }
    return 0;
}
poj2923

 完全背包

POJ - 3260

题意:一个人带着N种硬币买一件价值为T的东西。第i种硬币的面值为V[i],数量为C[i]。老板找零时可以使用这N种硬币,并且老板拥有无限数量的硬币。问此人付出的硬币数和找零得到的硬币数之和的最小值。

分析:完全背包+多重背包。首先是完全背包,将老板找零i元所需的最小硬币数f[i]求出;然后是多重背包,将此人付出i元所需的最小硬币数dp[i]求出;最后只需求(f[i]+dp[i+T])的最小值即可。

需要注意的是f与dp数组的大小设置。f的大小应该设置为maxV*maxV。证明如下:

如果找零大于maxV*maxV,说明找零了超过maxV个硬币。

假设硬币有k个(k>maxV),硬币序列为a1,a2,a3.....,ak。那么就有序列a1,a1,a2,a1,a2,a3,......,a1,a2,...ak这k个子序列。

由于硬币数最少,那么这k个子序列的元素和均不能被maxV整除(否则可以使用更少的面值为maxV的硬币替代)。

所以这k个子序列模maxV的余数只可能为0,1,2......,maxV-1这maxV种。由于k>maxV,必然有两个序列的余数相同,于是他们的差被maxV整除。于是这中间一段序列便可以被更少的maxV硬币替代,从而使得硬币数比k小。

(当然其实也可以不思考,凭感觉就可以了。。。。WA了再改大一点。。。比如我就是。。。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxT=10002;
const int maxV=122;
const int maxN=102;
int dp[maxT+maxV*maxV],f[maxV*maxV],c[15*maxN],v[15*maxN],C[maxN],V[maxN];
int main()
{
    int i,j,N,T,cnt,inf,lim,ans;
    while (scanf("%d%d",&N,&T)!=EOF)
    {
        lim=0;
        cnt=0;
        memset(f,60,sizeof(f));
        memset(dp,60,sizeof(dp));
        inf=dp[0];
        f[0]=0;
        dp[0]=0;
        for (i=1;i<=N;i++) scanf("%d",&C[i]); 
        for (i=1;i<=N;i++) scanf("%d",&V[i]); 
        for (i=1;i<=N;i++) 
        {
            lim=max(lim,C[i]);
            for (j=1;(j<<1)<=V[i];j<<=1)
            {
                c[++cnt]=j*C[i];
                v[cnt]=j;
            }
            j--;
            if (j!=V[i]) 
            {
                c[++cnt]=(V[i]-j)*C[i];
                v[cnt]=V[i]-j;
            }
        }
        for (i=1;i<=N;i++)
        for (j=C[i];j<=lim*lim;j++)
            f[j]=min(f[j],f[j-C[i]]+1);
        for (i=1;i<=cnt;i++)
        for (j=T+lim*lim;j>=c[i];j--)
            dp[j]=min(dp[j],dp[j-c[i]]+v[i]);
        ans=inf;
        for (i=0;i<=lim*lim;i++) ans=min(ans,dp[i+T]+f[i]);
        if (ans!=inf) printf("%d\n",ans);
        else printf("-1\n");
    }
    return 0;    
}
poj3260

 HDU - 1712

题意:有N个课程,M个单位的时间。在第i个课程里投入j个单位时间能够得到A[i][j]的收益。问能够得到的最大收益是多少。

分析:分组背包问题,每个课程为一个组,投入不同的时间相互冲突。将j看作体积,A[i][j]看作价值,做分组背包。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=105;
int dp[maxn],A[maxn][maxn];
int main()
{
    int i,j,k,N,M;
    while (scanf("%d%d",&N,&M)!=EOF && (N!=0 || M!=0))
    {
        memset(dp,0,sizeof(dp));
        for (i=1;i<=N;i++) 
        for (j=1;j<=M;j++) scanf("%d",&A[i][j]);
        for (i=1;i<=N;i++)//分组背包 
        for (j=M;j>=1;j--)
        for (k=1;k<=j;k++) dp[j]=max(dp[j],dp[j-k]+A[i][k]);
        printf("%d\n",dp[M]);
    }
    return 0;
}
hdu1712

 HDU - 3810

题意:有N个地点,每个地点有怪物,击杀第i个地点的怪物需花费时间t[i],可获得金钱g[i]。可以在一个地点和与它链接的地点之间移动。问得到M单位金钱所需花费的最小时间。

分析:分组背包,优先队列模拟01背包。

首先运用并查集,将所有地点分割为若干个连通块,每个连通块为一组做分组背包就是答案了。

但是T<=1e9,无法开1e9的数组,不能直接按照常规做法进行01背包。

使用优先队列模拟01背包的过程。每次选出当前获得金钱最多的状态进行更新,对于这个状态,由选或不选更新出两个新状态,再继续更新。(感觉有点像堆优化dij)

这样的时间复杂度是2^N,N<=50,需要剪枝。其中一个剪枝是,若两个状态i,j,T[i]<T[j]并且G[i]>G[j],显然不需要继续考虑j状态。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int INF=0x7fffffff;
const int maxn=52;
int fa[maxn],t[maxn],g[maxn];
struct block
{
    int t,g;
    block(int tt,int gg)
    {
        t=tt;g=gg;
    }
    bool operator < (const block &x) const
    {
        if (g==x.g) return t>x.t;
        return g<x.g;
        //价值大的排在前面,同价值情况时间时间短的排在前面 
    }
};
int find(int x)
{
    if (x==fa[x]) return x;
    return fa[x]=find(fa[x]);
}
priority_queue<block> q1,q2;
int main()
{
    int n,m,i,j,tt,T,k,c,mi,ans;
    scanf("%d",&T);
    for (tt=1;tt<=T;tt++)
    {
        scanf("%d%d",&n,&m);
        for (i=1;i<=n;i++) fa[i]=i;
        ans=INF;
        for (i=1;i<=n;i++)
        {
            scanf("%d%d%d",&t[i],&g[i],&k);
            while (k--)
            {
                scanf("%d",&c);
                fa[find(i)]=find(c);
            }
        }
        for (i=1;i<=n;i++)
        {
            if (fa[i]!=i) continue;
            while (!q1.empty()) q1.pop();
            while (!q2.empty()) q2.pop();
            q1.push(block(0,0)); 
            for (j=1;j<=n;j++)
            {
                if (find(j)!=i) continue;
                while (!q1.empty())
                {
                    
                    block x=q1.top();
                    q1.pop();
                    if (x.g>=m)//当此时价值已满足要求 
                    {
                        ans=min(ans,x.t);//更新答案 
                        continue;
                    }
                    if (x.t>=ans) continue;//当此时时间大于当前最优答案,不再考虑 
                    q2.push(x);//不选择该物品 
                    
                    x.t+=t[j];
                    x.g+=g[j];
                    if (x.g>=m)
                    {
                        ans=min(ans,x.t);
                        continue;
                    }
                    if (x.t>=ans) continue;
                    q2.push(x);//选择该物品 
                }
                mi=INF;
                while (!q2.empty())
                {
                    block x=q2.top();
                    q2.pop();
                    if (x.t>=mi) continue;//若价值较小并且时间长,则略过 
                    q1.push(x);//复制至q1 
                    mi=x.t;//更新最小时间 
                } 
            } 
        }
        if (ans==INF) printf("Case %d: Poor Magina, you can't save the world all the time!\n",tt);
        else printf("Case %d: %d\n",tt,ans);
    }
    return 0;
} 
hdu3810

 HDU - 3535

题意:有n个工作的集合,每个集合中有若干项工作。这些集合有三类,0类要求在这些工作中至少选取一个,1类要求至多选取一个,2类没有要求。每个工作i完成需要时间c[i],完成后获得快乐值g[i]。问在工作时间不超过T的情况下,满足所有要求能够取得的最大快乐值。

分析:多种背包混合。

定义数组dp[i][j],代表计算完前i个工作集合后,在工作时间不超过j的情况下能够取得的最大快乐值。初始化dp为负无穷,dp[0][]为0。

0类:至少选取一个。状态转移方程dp[i][j]=max(dp[i][j],dp[i-1][j-c[k]]+g[k],dp[i][j-c[k]]+g[k])

1类:至多选取一个。要先将dp[i-1]复制到dp[i],状态转移方程dp[i][j]=max(dp[i][j],dp[i-1][j-c[k]]+g[k])

2类:没有要求。直接01背包,不过要先将dp[i-1]复制到dp[i],状态转移方程dp[i][j]=max(dp[i][j],dp[i][j-c[k]]+g[k])

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=102;
int dp[maxn][maxn],c[maxn],g[maxn];
int main()
{
    int i,j,k,n,m,s,T;
    while (scanf("%d%d",&n,&T)!=EOF)
    {
        memset(dp,128,sizeof(dp));
        for (j=0;j<=T;j++) dp[0][j]=0;
        for (i=1;i<=n;i++)
        {
            scanf("%d%d",&m,&s);
            for (j=1;j<=m;j++) scanf("%d%d",&c[j],&g[j]);
            if (s==0)//至少做一项 
            {
                for (k=1;k<=m;k++) 
                for (j=T;j>=c[k];j--) dp[i][j]=max(dp[i][j],max(dp[i-1][j-c[k]]+g[k],dp[i][j-c[k]]+g[k]));
            }
            else if (s==1)//至多做一项
            {
                for (j=0;j<=T;j++) dp[i][j]=dp[i-1][j];
                for (k=1;k<=m;k++)  
                for (j=T;j>=c[k];j--) dp[i][j]=max(dp[i][j],dp[i-1][j-c[k]]+g[k]);
            }
            else if (s==2)//01背包
            {
                for (j=0;j<=T;j++) dp[i][j]=dp[i-1][j];
                for (k=1;k<=m;k++)
                for (j=T;j>=c[k];j--) dp[i][j]=max(dp[i][j],dp[i][j-c[k]]+g[k]);
            }
        }
        //printf("ans: ");
        if (dp[n][T]<0) printf("-1\n");
        else printf("%d\n",dp[n][T]);
    }
    return 0;
} 
hdu3535

 

posted @ 2021-02-27 23:27  lsy_kk  阅读(42)  评论(0编辑  收藏  举报