腾讯 小Q的歌单

最近开始多看一看名企曾经出过的编程题。今天看了一个动态规划的题,非常受用。题目从牛客上摘一下。

链接:https://www.nowcoder.com/questionTerminal/f3ab6fe72af34b71a2fd1d83304cbbb3
来源:牛客网

小Q有X首长度为A的不同的歌和Y首长度为B的不同的歌,现在小Q想用这些歌组成一个总长度正好为K的歌单,每首歌最多只能在歌单中出现一次,在不考虑歌单内歌曲的先后顺序的情况下,请问有多少种组成歌单的方法。

输入描述:

每个输入包含一个测试用例。
每个测试用例的第一行包含一个整数,表示歌单的总长度K(1<=K<=1000)。
接下来的一行包含四个正整数,分别表示歌的第一种长度A(A<=10)和数量X(X<=100)以及歌的第二种长度B(B<=10)和数量Y(Y<=100)。保证A不等于B。

输出描述:

输出一个整数,表示组成歌单的方法取模。因为答案可能会很大,输出对1000000007取模的结果。

示例1

输入

5
2 3 3 3

输出

9

一道典型的动态规划问题。一开始想用左老师的换钱方法数问题套用,但是这里的问题是规定了两种歌的数量上限,自己写出来的是不限次数且同类中各个元素都一样的版本。看了牛客一个C++的帖子以后豁然开朗,写了一个python版本的,示例过了。至少把思路留一下:

K = int(input())
[A, X, B, Y] = list(map(int, input().split(' ')))

if X == 0 and Y == 0:
    print(1)

mod = 1000000007
dp = [[0 for col in range(K+1)] for row in range(X+Y+1)]
p = [0 for col in range(X+Y+1)]
for i in range(1, X+1):
    p[i] = A
for i in range(X+1, X+Y+1):
    p[i] = B

dp[0][0] = 1  # 初始化第一行,在没有纸币的时候,形成总和为0的有1种办法(就是都不取);总和为其他数值的,都无法达到,有0种办法
for i in range(1, K+1):
    dp[0][i] = 0

for i in range(1, X+Y+1):
    for j in range(0, K+1):
        if j >= p[i]:  # 当前要凑的这个数额j,可以由至少一个p[i]来完成
            dp[i][j] = (dp[i-1][j] + dp[i-1][j-p[i]]) % mod  # 两部分:第一部分,是指本行(i)一个都不取,照搬i-1时的方法数;
                                                                # 第二部分,指的是本行多用一个,则i-1行少用一个时的方法数
        else:
            dp[i][j] = dp[i-1][j] % mod

print(dp[X+Y][K])

关键点是dp二维列表的含义。每一行代表用的歌的个数,而它对应的长度在p列表里保存,也就是说有一个简单的对应关系(或者说不同的歌代表不同的行)。dp的每一列代表能凑成多长的总长。因此,行是X+Y+1行,列是K+1行,最后取的时候找dp[X+Y][K]就是要的答案了。

在这种动态规划外,一开始还看到网上普遍用的一种方法。这个矩阵构造得让我觉得有问题,不是动态规划的矩阵,到现在我也觉得这不像是dp方法。方法重点是利用两种歌其中一个的范围,用全排列的公式将所有相乘的结果相加。而全排列,是构造了杨辉三角矩阵,再查询这个矩阵得来的。多数网上方法都说这个也是动态规划,我不是很认同。一种形式的python代码如下:

# -*- coding:utf-8 -*-
mod = 1000000007
K = int(input())
A, X, B, Y = list(map(int, input().split()))
 
arr = [[0 for i in range(101)] for j in range(101)]
arr[0][0] = 1
for i in range(1,101):
    arr[i][0] = 1
    for j in range(1,101):
        arr[i][j] = (arr[i-1][j-1]+arr[i-1][j]) % mod
 
ans = 0
for i in range(X+1):
    if (i*A <= K and (K-A*i) % B == 0 and (K-A*i)//B <= Y):
        ans = (ans +(arr[X][i]*arr[Y][(K-A*i)//B]) % mod) % mod
print(ans)

其中在最后输出结果的时候,就是同时卡了几个条件,恰好能凑成K,并且两种歌的数量不超限而已。方法是很好很巧妙的,可以学习借鉴一下,不过这真是dp吗?还望懂行的大佬指教一二。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值