动态规划的背包问题

背包问题有许多
01背包:一种只能取一次
多重背包:一种可以取有限的次数
完全背包:一种可以取无数次


最近做dp做的快吐了···

先来个简单的01背包
luogu1060 自己找题吧···

直接上代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,v[30],w[30],dp[26][30000],ans;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>v[i]>>w[i];
    }
    for(int i=1;i<=m;i++)
        for(int j=i;j<=n;j++)
        {
            if(j-v[i]>=0)
                dp[i][j]=max(dp[i-1][j-v[i]]+w[i]*v[i],dp[i-1][j]);//dp方程
            else dp[i][j]=dp[i-1][j];
        }
    for(int i=1;i<=m;i++)
    {
        for(int j=i;j<=n;j++)
            ans=max(ans,dp[i][j]);
    }
    cout<<ans<<endl;
    return 0;
}

luogu1164
求方案数的
也是01背包

#include<iostream>
#include<cstdio>
#define maxn 10005
using namespace std;
int n,m,a[maxn],dp[maxn],mx;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    dp[0]=1;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=a[i];j--)//注意这里要倒着推,才能保证不会重复算
        {
            dp[j]=dp[j-a[i]]+dp[j];
        }
    cout<<dp[m]<<endl;
    return 0;
}

然后再来个完全背包的吧
luogu1616

#include<iostream>
#include<cstdio>
using namespace std;
int t,m,v[10005],tim[10005],dp[100005];
int main()
{
    cin>>t>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>tim[i]>>v[i];
    }
    for(int i=1;i<=t;i++)//以时间为第一重循环
    {
        int maxn=0;
        for(int j=1;j<=m;j++)
        {
            if(i-tim[j]>=0)
                maxn=max(maxn,dp[i-tim[j]]+v[j]);
            else maxn=max(maxn,dp[i]);//dp方程
        }
        dp[i]=maxn;//算出每个时间能达到的最大值
    }
    cout<<dp[t]<<endl;
    return 0;
}

然后多重背包
luogu1077 数据范围不大暴力枚举就行
多重背包好像可以二进制拆分优化
但这道求方案数的好像不太可以

粘代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define maxn 105
#define md 1000007
using namespace std;
int n,m,a[maxn],dp[maxn][maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=0;i<=m;i++) dp[i][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=j;k>=j-a[i];k--)
            {
                if(k>=0)
                {
                    dp[i][j]+=dp[i-1][k],dp[i][j]%=md;
                }
                else break;
            }
    printf("%d\n",dp[n][m]);
    return 0;
}

咳咳
这么多水题
来个难的
luogu1880石子合并
令人窒息的操作

其实就是线性dp
遇到环的问题先复制转化成线
然后倒推起点
一段一段搜
取各个段中间的值都遍历一遍
找最大值和最小值
其他的线性dp和这个思路差不多都

#include<iostream>
#include<cstdio>
#define INF 0x7fffff
using namespace std;
int n,a[205],b[205],mx,mn,dps[2005][2005],dpl[2005][2005];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i];//复制
    for(int i=1;i<=2*n;i++) b[i]=b[i-1]+a[i];//前缀和
    for(int i=2*n-1;i>=1;i--)//倒推起点
        for(int j=i+1;j<=i+n-1;j++)//长度为n的一段
        {
            dps[i][j]=INF;
            for(int k=i;k<j;k++)//遍历中间值
            {
                dps[i][j]=min(dps[i][j],dps[i][k]+dps[k+1][j]+b[j]-b[i-1]);//dp方程
                dpl[i][j]=max(dpl[i][j],dpl[i][k]+dpl[k+1][j]+b[j]-b[i-1]);
            }
        }
    mn=INF;
    for(int i=1;i<=n;i++)
    {
        mx=max(mx,dpl[i][i+n-1]);
        mn=min(mn,dps[i][i+n-1]);
    }
    cout<<mn<<endl<<mx<<endl;
    return 0;
}

以及luogu1057
也是一个环的问题
不过这个问题有些不同
思路就是这个人的等于左边传过来的加上右边传过来的
注意第一个和最后一个要单独拿出来

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,dp[35][35];

int main()
{
    cin>>n>>m;
    dp[1][0]=1;
    for(int i=1;i<=m;i++)
    {
        dp[1][i]=dp[2][i-1]+dp[n][i-1];
        dp[n][i]=dp[n-1][i-1]+dp[1][i-1];
        for(int j=2;j<n;j++)
            dp[j][i]=dp[j-1][i-1]+dp[j+1][i-1];
    }
    cout<<dp[1][m]<<endl;
    return 0;
}

luogu1063
依然是环的问题
和石子合并一个思路
也是一段段的推
在这提供两种写法
第一种:
以i为一段的长度,j为起点,i+j-1为终点,k为中间值
第二种:
以i为起点,j为终点,每段都是n,k为中间值

两种思想都一样
但是还是认为第二种思路更清晰
第一种比较迷容易写错

#include<iostream>
#include<cstdio>
#define maxn 300
using namespace std;
int n,a[maxn],dp[maxn][maxn],mx;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i+n]=a[i];
    }
    for(int i=2;i<=n+1;i++)
        for(int j=1;j<=2*n && i+j-1<=2*n ;j++)
            for(int k=j+1;k<i+j-1;k++)
            {
                int r=i+j-1;
                dp[j][r]=max(dp[j][r],dp[j][k]+dp[k][r]+a[j]*a[k]*a[r]);
                mx=max(mx,dp[j][r]);
            }//第一种 
    for(int i=2*n-1;i;i--)
        for(int j=i+1;j<i+n && j<=2*n;j++)
            for(int k=j-1;k>=i;k--)//这里正反无所谓
            {
                dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i]*a[k+1]*a[j+1]);
                mx=max(dp[i][j]);
            }//第二种 
    cout<<mx<<endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值