多重部分和问题

题目描述:

  • nnn中大小不同的数字aia_iai,每种数字mim_imi,判断是否可以从这些数字之中选出若干使它们的和恰好为KKK

限制条件:

  • 1≤n≤1001≤n≤1001n100
  • 1≤ai,mi≤1000001≤a_i,m_i≤1000001ai,mi100000
  • 1≤K≤1000001≤K≤1000001K100000

题解:

一般动态规划:

  • 步骤
    • dp数组含义dp[i][j]dp[i][j]dp[i][j]=用前iii种数字是否能拼成jjj
    • 初始条件dp[0][1−K]=False;dp[0−n][0]=Turedp[0][1-K]=False; dp[0-n][0]=Turedp[0][1K]=False;dp[0n][0]=Ture
    • 递推公式dp[i][j]={∃dp[i−1][j−k×ai]为真∣0≤k≤mi&k×ai≤j}dp[i][j]=\{∃ dp[i-1][j-k×a_i ]为真|0≤k≤m_i \& k×a_i≤j\}dp[i][j]={dp[i1][jk×ai]0kmi&k×aij}
    • 递推方向:从上向下
    • 结果dp[n][K]dp[n][K]dp[n][K]是否为真
  • 时间复杂度 O(K∑imi)O(K∑_im_i )O(Kimi)
  • 代码 :函数 solve1()solve1()solve1()

优化动态规划:

  • 上面的动态规划的优化方向
    • 第一: dp数组中只存取bool型结果会有不少浪费,可以用来存取更有效的信息
    • 第二:我们递推只使用用了从上到下的递推方向,我们可能可以使用左到右的递推方向来辅助
  • 步骤
    • dp数组含义dp[i][j]dp[i][j]dp[i][j]=用前iii种数字是拼成jjj,第i种数字最多剩余多少个,如果拼不成j就令其为-1
    • 初始条件dp[0][1−K]=−1;dp[0][0]=0,dp[1−n][0]=midp[0][1-K]=-1; dp[0][0]=0,dp[1-n][0]=m_idp[0][1K]=1;dp[0][0]=0,dp[1n][0]=mi
    • 递推公式dp[i][j]={midp[i−1][j]≥0−1j&lt;aiordp[i][j−ai]≤0dp[i][j−ai]−1其他dp[i][j]=\begin{cases} m_i &amp; dp[i-1][j]≥0 \\ -1 &amp;j&lt;a_i or dp[i][j-a_i ]≤0\\ dp[i][j-a_i ]-1 &amp;其他\\\end{cases}dp[i][j]=mi1dp[i][jai]1dp[i1][j]0j<aiordp[i][jai]0
    • 递推方向:从上向下,从左到右
    • 结果dp[n][K]dp[n][K]dp[n][K]是否为真
  • 时间复杂度 O(nK)O(nK )O(nK)
  • 代码 :函数 solve2()solve2()solve2()

代码:

#include <iostream>
#define Max_N   105
#define Max_K   100005
using namespace std;

int n,K;
int a[Max_N],m[Max_N];
bool dp[Max_N][Max_K];
int Dp[Max_N][Max_K];
int DP[Max_K];

//基本动态规划
void solve1()
{
    //初始化
    for(int i=1; i<=K; i++)
        dp[0][i]=false;
    for(int i=0; i<=n; i++)
        dp[i][0]=true;
    //递推
    for(int i=1; i<=n; i++)
        for(int j=1; j<=K; j++)
            for(int k=0; k<=m[i]&&k*a[i]<=j; k++)
                dp[i][j]|=dp[i-1][j-k*a[i]];
    //结果
    if(dp[n][K])
        cout<<"Yes"<<endl;
    else
        cout<<"NO"<<endl;
}
//优化动态规划
void solve2()
{
    //初始化
    for(int i=1; i<=K; i++)
        Dp[0][i]=-1;
    for(int i=1; i<=n; i++)
        Dp[i][0]=m[i];
    Dp[0][0]=0;
    //递推
    for(int i=1; i<=n; i++)
        for(int j=1; j<=K; j++)
        {
            if(Dp[i-1][j]>=0)
                Dp[i][j]=m[i];
            else if(j<a[i]||Dp[i][j-a[i]]<=0)
                Dp[i][j]=-1;
            else
                Dp[i][j]=Dp[i][j-a[i]]-1;
        }

    if(Dp[n][K]>=0)
        cout<<"YES"<<endl;
    else
        cout<<"NO"<<endl;
}

//内存优化,数组从用
void solve3()
{
    for(int i=1; i<=K; i++)
        DP[i]=-1;
    DP[0]=0;
    for(int i=1; i<=n; i++)
        for(int j=0; j<=K; j++)
        {
            if(DP[j]>=0)
                DP[j]=m[i];
            else if(j<a[i]||DP[j-a[i]]<=0)
                DP[j]=-1;
            else
                DP[j]=DP[j-a[i]]-1;
        }
    if(DP[K]>=0)
        cout<<"YES"<<endl;
    else
        cout<<"NO"<<endl;
}

int main()
{
    cin>>n>>K;
    for(int i=1; i<=n; i++)
        cin>>a[i]>>m[i];
    solve1();
    solve2();
    solve3();
    return 0;
}
/*
3 17
3 3
5 2
8 2
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值