背包DP合辑

POJ 3268 Bookshelf 2 01背包

01背包,注意可以超过上限,因此枚举容量应该 for(j=s+h1;j>=1;j)

POJ 2392 Space Elevator 多重背包 高度限制

每个物品都一个高度上限,那么先对物品的高度排序再进行普通的多重背包。

POJ 1976 A Mini Locomotive 01背包

看似很复杂,一开始甚至想到多子段和最大去了。。 然后发现因为能取的是固定的,所以不如固定取某个数就要连它前面的m-1个一起取,那么dp方程就不难写了。
dp[i][j] 表示取i段到j位置(j可能不取)时的最大值。
代码如下

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;
const int maxn = 5e4+100;
int dp[4][maxn],sum[maxn];
int n,m,t;


int main()
{
    cin>>t;
    while (t--)
    {
        memset(dp,0,sizeof dp);
        memset(sum,0,sizeof sum);
        cin>>n;
        for (int i=1;i<=n;i++)
        {
            int num;
            cin>>num;
            sum[i] = sum[i-1] + num;
        }
        cin>>m;
        for (int i=1;i<=n;i++)
        {
            int tot = sum[i];
            if (i>=m) tot -= sum[i-m];
            dp[1][i] = max(dp[1][i-1],tot);
            if (i>=m)
            {
                dp[2][i] = max(dp[2][i-1], tot + dp[1][i-m]);
                dp[3][i] = max(dp[3][i-1], tot + dp[2][i-m]);
            }
        }
        int ans = max(max(dp[1][n],dp[2][n]),dp[3][n]);
        cout<<ans<<endl;
    }
    return 0;
}

POJ 1745 Divisibility 01背包 余数

这题竟然一开始没想到。。以后碰到整除的要多想想余数啊,因为余数的范围不超过100,所以dp维护余数即可,还是01背包。然后注意不要让余数小于0,因此多弄一个(r%k+k)%k 即可。

POJ 2923 Relocation 01背包 状压dp

01背包+ 状压dp
用01背包算出那些能让c1,c2一次搬完的状态,然后用这些状态进行状压dp。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 100;
int a[maxn],n,m,c1,c2,dp[10000];
int s[10000],cnt;

bool judge(int state)
{
    int tot = 0, tmp = 0;
    memset(dp,0,sizeof dp);
    dp[0]=true;
    for (int i=1;i<=n;i++)
    {
        if (  (state&(1<<(i-1))) !=0 )
        {
            tot += a[i];
            for (int j=c1;j>=a[i];j--)
                if (dp[j-a[i]]) dp[j]=true,tmp = max(tmp,j);
        }
    }
    if (tot - tmp > c2)   return false;
    else return true;
}

int main()
{
    int t;
    cin>>t;
    for (int tt=1;tt<=t;tt++)
    {
        cnt = 0;
        scanf("%d%d%d",&n,&c1,&c2);
        for (int i=1;i<=n;i++) scanf("%d",&a[i]);
        int X = 1<<n;
        for (int i=1;i<X;i++) if (judge(i)) s[cnt++]=i;
        memset(dp,0x3f,sizeof dp);
        dp[0]=0;
        for (int i=0;i<X;i++)
        {
            for (int j=0;j<cnt;j++)
                dp[i|s[j]] = min(dp[i|s[j]] , dp[i]+1);
        }
        printf("Scenario #%d:\n%d\n\n",tt,dp[X-1]);
    }
    return 0;
}

POJ 1837 Balance 01背包

01背包,对于每个物品可以选择任何一个钩子,价值为w[i]*c[j],钩子在左边价值就是负的,否则就是正的。算一下范围,价值总和最大为8000左右,那么把10000作为原点,dp[i][j]表示计算到第i个物品时,j这种价值有多少情况。用01背包计算出所有总和的情况,最后dp[m][10000] 就是答案了,还可以用滚动数组优化一下空间。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 30;
const int maxm = 20010;
int dp[maxn][maxm];
int n,m,c[maxn];


int main()
{
    dp[0][10000]=1;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&c[i]);
    for (int i=1;i<=m;i++)
    {
        int val,v;
        scanf("%d",&val);
        for (int j=1;j<=n;j++)
        {
            int v = c[j] * val;
            for (int k=0;k<=20000;k++)
            {
                if (dp[i-1][k])
                    dp[i][k+v] += dp[i-1][k];
            }
        }
    }
    printf("%d\n",dp[m][10000]);
    return 0;
}

POJ 1948 Triangular Pasture 01背包

面积用海伦公式算。
truncated是截断的意思,就是直接去掉小数位。
dp[i][j]表示第1条边长度为i,第二条为j,那么第三条就是sum-i-j。
那么枚举i,j,当dp[i][j]为true时,dp[i+L][j]=dp[i][j+L]=true。注意逆序循环。防止同一根棒用多次。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 50;
const int maxm = 2100;
int sum,n,l,ans=-1;
bool dp[maxm][maxm];

int main()
{
    dp[0][0]=true;
    scanf("%d",&n);
    int t=1;
    while (n--)
    {
        scanf("%d",&l);
        sum += l;
        for (int i=sum;i>=0;i--)
            for (int j=sum;j>=0;j--)
                if (dp[i][j])
                {
                    dp[i+l][j]=true;
                    dp[i][j+l]=true;
                }
    }
    for (int i=1;i<sum;i++)
            for (int j=1;j<sum;j++)
                if (dp[i][j])
                {
                    int a=i,b=j,c=sum-i-j;
                    if (c>0 && a+b>c && a+c>b && b+c>a)
                    {
                        double len = sum/2.0;
                        int area = int(sqrt(len*(len-a)*(len-b)*(len-c))*100);
                        ans=max(ans,area);
                    }
                }
    printf("%d\n",ans);
    return 0;
}

POJ 1014 多重背包 倍增优化 单调队列优化

倍增就是每次取1,2,4,8,…个这样可以组合出n个包括n个以内的任何数字。而不用从 1...num[i] 枚举来dp。
至于单调队列。。先放着吧。。

const int maxn = 121000;
bool dp[maxn];
int n,m,num[10],sum;

void solve(int v)
{
    for (int i=sum;i>=v;i--) dp[i]|=dp[i-v];
}

int main()
{
    int t=0,cnt=0;
    while (1)
    {
        memset(dp,0,sizeof dp);
        dp[0]=true;
        sum = 0;
        for (int i=1;i<=6;i++) scanf("%d",&num[i]),sum+=num[i]*i;
        if (!sum) break;
        printf("Collection #%d:\n",++t);
        if (sum&1)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        for (int i=1;i<=6;i++)
        {
            int j=1;
            while (num[i] > j)
            {
                solve(i*j);
                num[i]-=j;
                j<<=1;
            }
            solve(num[i]*i);
        }
        if (dp[sum/2])
            printf("Can be divided.\n\n");
        else
            printf("Can't be divided.\n\n");
    }
    return 0;
}

HDU 1171 Big Event in HDU 多重背包可行性

原来的选最优价值的多重背包问题,用倍增优化的复杂度是 O(Vlogn[i]) 。优先队列优化可以达到 O(NV) 。不过实现起来比较麻烦。
但是涉及到可行性的多重背包。比如能不能拼出这个重量。就可以很方便的达到 O(NV) 的复杂度。
dp[i][j] 表示 用到第i个物品时,拼出重量j后,i还剩多少个。
1. dp[i-1][j] >= 0 ,说明前面已经拼出重量j,不用再消耗i,因此dp[i][j] = m[i]
2. j < v[i] || dp[i-1][j-v[i]] <0 说明v[i]比j大 或者 用前i-1种无法拼出,因此再用上一个v[i]还是不能拼出 dp[i][j] = -1
3. dp[i-1][j-v[i]]>=0 此时可以拼出j,dp[i][j] = dp[i-1][j-v[i]] -1

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 60;
int dp[ 5000 * 60];
int n,v,m,sum;

int main()
{
    while (scanf("%d",&n) && n>=0)
    {
        sum = 0;
        memset(dp,-1,sizeof dp);
        dp[0]=0;
        for (int i=1;i<=n;i++)
        {
            scanf("%d%d",&v,&m);
            sum += v*m;
            for (int j=0;j<=sum;j++)
            {
                if (dp[j]>=0)
                    dp[j] = m;
                else if (j<v || dp[j-v]<0)
                    dp[j] = -1;
                else if (dp[j-v]>=0)
                    dp[j] = dp[j-v]-1;
            }
        }
        int tmp = (sum+1)/2;
        for (int i=tmp;i<=sum;i++)
        {
            if (dp[i]>=0)
            {
                printf("%d %d\n",i,sum-i);
                break;
            }
        }
    }
    return 0;
}

POJ 3093 Margaritas on the River Walk 01背包 技巧

先给所有物品从小到大排序,然后倒序枚举第i个物品是没放入背包的物品中最小的,那么明显 1i1 之间的都直接放入,然后对 i+1n 之间的做背包,他们是可能会放入的。
为什么要倒序做?因为这样 i+1n 之间的背包才不会重复做,否则如果是正序的话,第一次是做 2n 之间的背包,第二次是做 3n 之间的背包,那么因为2不能放入,又要重新做一次背包,如果是逆序的话可以继续上一次的结果做背包。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 1010;
typedef long long LL;
LL dp[maxn];
int n,v,d,w[maxn],sum;

int main()
{
    int t,tt=0;
    cin>>t;
    while (t--)
    {
        LL ans=0;
        sum=0;
        scanf("%d%d",&n,&d);
        for (int i=1;i<=n;i++) scanf("%d",w+i),sum+=w[i];
        sort(w+1,w+1+n);
        if (w[1] > d)
        {
            printf("%d 0\n",++tt);
            continue;
        }
        memset(dp,0,sizeof dp);
        dp[0]=1;
        for (int i=n;i>=1;i--)
        {
            sum -= w[i];
            for (int j=0; j<w[i] && d-sum-j>=0;j++)
                ans += dp[d-sum-j];
            for (int j=d;j>=w[i];j--) dp[j] += dp[j-w[i]];
        }
        printf("%d %d\n",++tt,ans);
    }

    return 0;
}

HDU 3033 I love sneakers! 分组背包变形

分组背包变形,不是每组只能取一个而是每组至少取一个。
那么用dp[i][j]表示取到第i组,所用容量为j时的最大价值。
转移方程

dp[i][j]=max(dp[i][j],dp[i1][jw[i][k]]+v[i][k],dp[i][jw[i][k]]+v[i][k])(m>=j>=w[i][k])

这样就能做到至少取一个。

HDU 3535 AreYouBusy 混合分组背包

多种分组类型,有必须取一种的,有至多取一种的,有任意取的。
dp[i][j]表示取到第i组j容量下的价值。
其实状态很好想,就是转移有点麻烦。
1. 必须取一种,那么先把自己这一层的dp[i][j]全部变成-inf,

for (int j=0;j<=t;j++) dp[i][j] = -inf;
for (int j=1;j<=m;j++)
    for (int k=t;k>=c[j];k--)
    {
        dp[i][k] = max(dp[i][k],dp[i][k-c[j]]+g[j]);//1
        dp[i][k] = max(dp[i][k],dp[i-1][k-c[j]]+g[j]);//2
    }
然后可以从上一层开始转移,这样如果不是从上一层加上一个这一层的一个物品转移过来的,自己这里还是-inf,就是无解。还可以从自己这层转移,只有在dp[i][k-c[j]]!=-inf下才能转移,说明已经加入了这层的物品。还有一个坑点就是可能有c为0的物品,那么要把第一条放在前面,否则一个物品会加两次。

2. 至多取一种,那么只能从上一层转移。
3. 任意取就是普通的01背包了。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 110;
const int inf = 0x3f3f3f3f;
int dp[maxn][maxn];
int n,t,m,s,c[maxn],g[maxn];

int main()
{
    while (scanf("%d%d",&n,&t)!=EOF)
    {
        memset(dp,0,sizeof dp);
        int sum = 0;
        for (int i=1;i<=n;i++)
        {
            scanf("%d%d",&m,&s);
            for (int j=1;j<=m;j++) scanf("%d%d",&c[j],&g[j]);
            if (s==0)//at least one
            {
                for (int j=0;j<=t;j++) dp[i][j] = -inf;
                for (int j=1;j<=m;j++)
                    for (int k=t;k>=c[j];k--)
                    {
                        dp[i][k] = max(dp[i][k],dp[i][k-c[j]]+g[j]);
                        dp[i][k] = max(dp[i][k],dp[i-1][k-c[j]]+g[j]);
                    }
            }
            else if (s==1)//at most one
            {
                for (int j=0;j<=t;j++) dp[i][j] = dp[i-1][j];
                for (int j=1;j<=m;j++)
                    for (int k=t;k>=c[j];k--)
                        dp[i][k] = max(dp[i][k],dp[i-1][k-c[j]]+g[j]);
            }
            else
            {
                for (int j=0;j<=t;j++) dp[i][j] = dp[i-1][j];
                for (int j=1;j<=m;j++)
                    for (int k=t;k>=c[j];k--)
                        dp[i][k] = max(dp[i][k],dp[i][k-c[j]]+g[j]);
            }
        }
        int ans=max(-1,dp[n][t]);
        printf("%d\n",ans);
    }
    return 0;
}

POJ 2184 Cow Exhibition 有负数的01背包

这题可以把s当做容量,f当做价值来做01背包,因为有正负,所以要把背包的0点取到最大值处,让全是负数的时候也能存下,这题就是1e5了。然后枚举的顺序在负数是反过来的。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 110;
const int inf = 0x3f3f3f3f;
const int tot = 100000;
int n,s[maxn],f[maxn];
int dp[tot*3+100];

int main()
{
    int ans=0;
    for (int i=0;i<=tot*3;i++) dp[i]=-inf;
    dp[tot] = 0;
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d%d",&s[i],&f[i]);
    for (int i=1;i<=n;i++)
    {
        if (s[i]>=0)
            for (int j=2*tot;j>=s[i];j--)
            {
                if (dp[j-s[i]]!=-inf)
                {
                    dp[j] = max(dp[j],dp[j-s[i]]+f[i]);
                    if (j>=tot && dp[j] >= 0)
                        ans = max(ans,j+dp[j]-tot);
                }

            }
        else
            for (int j=0;j<=2*tot + s[i];j++)
            {
                if (dp[j-s[i]]!=-inf)
                {
                    dp[j] = max(dp[j],dp[j-s[i]]+f[i]);
                    if (j>=tot && dp[j] >= 0)
                        ans = max(ans,j+dp[j]-tot);
                }
            }
    }
    printf("%d\n",ans);
    return 0;
}

HDU 2639 Bone Collector II 第k大01背包

给原来的背包加一维k,表示第一大到第k大分别是多少。然后转移的时候把dp[j-c[i]][1..k] + v[i]和dp[j][1..k]这两组数字重新排列,取前面最大的K个放入dp[j][1..k]。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 110;
int n,v,k,c[maxn],val[maxn];
int dp[1110][35];
int a[maxn],b[maxn];

int main()
{
    int t;
    cin>>t;
    while (t--)
    {
        memset(dp,0,sizeof dp);
        scanf("%d%d%d",&n,&v,&k);
        for (int i=1;i<=n;i++) scanf("%d",&val[i]);
        for (int i=1;i<=n;i++) scanf("%d",&c[i]);
        for (int i=1;i<=n;i++)
        {
            for (int j=v;j>=c[i];j--)
            {
                for (int kk=1;kk<=k;kk++)
                {
                    a[kk] = dp[j][kk];
                    b[kk] = dp[j-c[i]][kk] + val[i];
                }
                a[k+1]=b[k+1]=-1;
                int aa=1,bb=1,cc=1;
                while (cc<=k && (aa<=k || bb<=k))
                {
                    if (a[aa] > b[bb])
                        dp[j][cc] = a[aa++];
                    else
                        dp[j][cc] = b[bb++];
                    if (dp[j][cc] != dp[j][cc-1]) cc++;//重复的不算
                }
            }
        }
        printf("%d\n",dp[v][k]);
    }
    return 0;
}

HDU 3236 Gift Hunting 01背包变形

两张优惠券相当于两个背包,一次免费的机会我们可以再开一维表示是否使用掉,必须取的物品可以通过把它的价值增大到大于总和,这样取到最大价值的时候一定会有它,最后再判断一下取的个数就可以了。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 400;
const int up = 1e6;
int v1,v2,n;
int dp[550][60][2];


int main()
{
    int tt=0;
    while (scanf("%d%d%d",&v1,&v2,&n),v1||v2||n)
    {
        memset(dp,0,sizeof dp);
        int cnt=0;
        for (int i=1;i<=n;i++)
        {
            int p,h,s;
            scanf("%d%d%d",&p,&h,&s);
            if (s==1) h += up,cnt++;
            for (int j=v1;j>=0;j--)
                for (int k=v2;k>=0;k--)
                {
                    dp[j][k][1] = max(dp[j][k][1],dp[j][k][0]+h);
                    if (k>=p)
                    {
                        dp[j][k][1] = max(dp[j][k][1],dp[j][k-p][1]+h);
                        dp[j][k][0] = max(dp[j][k][0],dp[j][k-p][0]+h);
                    }
                    if (j>=p)
                    {
                        dp[j][k][1] = max(dp[j][k][1],dp[j-p][k][1]+h);
                        dp[j][k][0] = max(dp[j][k][0],dp[j-p][k][0]+h);
                    }
                }
        }
        int ans = dp[v1][v2][1]/up;
        printf("Case %d: ",++tt);
        if (ans != cnt)
            printf("-1\n\n");
        else
            printf("%d\n\n",dp[v1][v2][1]-ans*up);
    }
    return 0;
}

HDU 2955 Robberies 01背包 概率

因为给的是被抓的概率,而计算被抓的概率又很麻烦,那不如计算不被抓的概率,就是把(1-p[i])都乘起来。
然后把钱当做容量,计算每种钱下的不被抓的最大概率就行了。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 200;
const double eps = 1e-8;
long double dp[110*110];
int n;
long double p;
int m[maxn];
long double s[maxn];

int main()
{
    int t;
    cin>>t;
    while (t--)
    {
        memset(dp,0,sizeof dp);
        dp[0]=1;
        int tot=0;
        cin>>p>>n;
        for (int i=1;i<=n;i++) cin>>m[i]>>s[i],tot+=m[i];
        for (int i=1;i<=n;i++)
            for (int j=tot;j>=m[i];j--)
                dp[j] = max(dp[j],dp[j-m[i]] * (1-s[i]));
        for (int i=tot;i>=0;i--)
            if (dp[i]+eps > 1-p)
            {
                printf("%d\n",i);
                break;
            }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值