zoukankan      html  css  js  c++  java
  • [luogu p1164] 小A点菜

    传送门

    小A点菜

    题目背景

    uim神犇拿到了uoira(镭牌)后,立刻拉着基友小A到了一家......餐馆,很低端的那种。

    uim指着墙上的价目表(太低级了没有菜单),说:"随便点"。

    题目描述

    不过uim由于买了一些辅(e)辅(ro)书,口袋里只剩(M)((M le 10000))

    餐馆虽低端,但是菜品种类不少,有(N)((N le 100)),第(i)种卖(a_i)((a_i le 1000))。由于是很低端的餐馆,所以每种菜只有一份。

    小A奉行"不把钱吃光不罢休",所以他点单一定刚好吧uim身上所有钱花完。他想知道有多少种点菜方法。

    由于小A肚子太饿,所以最多只能等待(1)秒。

    输入输出格式

    输入格式

    第一行是两个数字,表示(N)(M)

    第二行起(N)个正数(a_i)(可以有相同的数字,每个数字均在(1000)以内)。

    输出格式

    一个正整数,表示点菜方案数,保证答案的范围在(int)之内。

    输入输出样例

    输入样例 #1

    4 4
    1 1 2 2
    

    输出样例 #1

    3
    

    分析

    01背包。(好吧没有采药裸,这题实际上有点变化)
    首先我们设(dp_{i,j})为点前(i)个菜花费(j)元钱的方案数,那么状态转移方程有(3)种情况:(j > a_i)(j = a_i)(j < a_i)。分别考虑:

    如果(j < a_i),说明当前的钱买不了这个菜了。那还能怎么办?方案数和不买这个菜的数一样,也就是(dp_{i,j} = dp_{i - 1, j})

    如果(j = a_i),说明当前的钱恰好可以买这个菜,也就是多了一种买菜方案。(dp_{i,j} = dp_{i - 1,j} +1)

    如果(j > a_i),那么我们可以吃,可以不吃。吃的方案数是(dp_{i - 1,j - a_i}),不吃的还是(dp_{i-1,j})。答案就是两者之和:(dp_{i - 1,j - a_i} + dp_{i-1,j})

    细心的同学可能会发现,第二个条件和第三个条件可以合并考虑。(j = a_i)的时候,如果把他用(j > a_i)的方法计算,结果是(dp_{i - 1,0} + dp_{i-1,j}),和原来的(dp_{i,j} = dp_{i - 1,j} +1)对比一下,我们只要初始化所有的(dp_{i,0})都为1,第二条和第三条就能合并考虑。

    二维版本的递推方程:

    (dp_{i,j}=egin{cases}dp_{i - 1, j}&j < a_i\dp_{i - 1,j - a_i} + dp_{i-1,j}&j ge a_iend{cases})

    接下来考虑降维。采用滚动数组的思想,(dp_{i-1})降维成(pre)(dp_{i})降维成(now)

    (now_j=egin{cases}pre_j&j < a_i\pre_{j - a_i} + pre_j&j ge a_iend{cases})

    但是实际过程中,我们优化成了一个一维数组。咋肥四呢?首先我们先把(now)(pre)都无脑换成(dp)

    (dp_j = egin{cases}dp_j&j < a_i\dp_{j - a_i} + dp_j&j ge a_iend{cases})

    也就是说,当(j ge a_i)的时候,(dp_j = dp_j + dp_{j-a_i})

    但是这个递推方程仍然可以画上等号吗?
    首先我们令(now_j ightarrow dp_j),首先当(now_j)没更新的时候,(now_j = pre_j),但是我们还得让(pre_{j - a_i} = dp_{j-a_i}),也就是说,我们需要让(dp_{j-a_i})保持在(i - 1)层状态,大白话,我们不能让(dp_{j-a_i})更新。但是(j-a_i le j),如果我们内部循环是正这遍历的,求(dp_j)的时候,(dp_{j - a_i})必然会更新,这会导致结果的错误。所以我们必须让内部循环倒着遍历,这样(dp_{j - a_i})还是旧的(i - 1)层,结果就正确啦。

    刚刚证明完了可以画等号,只是需要倒序遍历,也就是说(j ge a_i)的时候,(dp_j = dp_j + dp_{j-a_i})这个结论是正确的。这下整个问题就会变得异常简单,我们只需要让(j)(m)倒序遍历到(a_i),然后dp[j] += dp[j - a[i]]

    上代码。

    代码

    #include <iostream>
    #include <cstdio>
    
    const int maxn = 105;
    const int maxm = 10005;
    int n, m, a[maxn], dp[maxm] = {1};//别忘了,dp[0]必须是1哦!
    
    int main() {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
    
        for(int i = 1; i <= n; i++)
            for(int j = m; j >= a[i]; j--)
                dp[j] += dp[j - a[i]];
                
        printf("%d
    ", dp[m]);
        return 0;
    }
    

    评测结果

    AC 100R31579801

  • 相关阅读:
    IAR EWARM PRINTF/SCANF FORMATTER
    Windows Self Signed Driver
    Remove a Driver Package from the Driver Store
    CMSIS-DAP调试器
    CHM文件无法查看内容解决办法
    HRESULT 0x80131515 解决方法
    dmalloc 原文 翻译整理
    Linux错误代码
    Windows 错误代码
    调码王版本历史
  • 原文地址:https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p1164.html
Copyright © 2011-2022 走看看