小Q的歌单

[编程题] 小Q的歌单

【题目描述】 小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
输出例子1:
9

解题思路:
1.令 a*A+b*B=K ( 0 ≤ a ≤ X , 0 ≤ b ≤ Y ) \fbox{a*A+b*B=K} (0≤a≤X,0≤b≤Y) a*A+b*B=K0aX0bY,获得集合 ( a , b ) (a,b) a,b
2.对每种集合都 C ( X , a ) ∗ C ( Y , b ) C(X, a) * C(Y,b) C(X,a)C(Y,b)种组合
3.最后进行累加求和,得到一共多少种不同的组合类型。

难点:

  1. C ( n , k ) C(n,k) C(n,k),n、k的范围是1~100
    因为 C ( k n ) = C ( k − 1 n − 1 ) + C ( k − 1 n ) C\begin{pmatrix} k \\ n \\ \end{pmatrix} = C\begin{pmatrix} k - 1 \\ n - 1 \\ \end{pmatrix} + C\begin{pmatrix} k - 1 \\ n \\ \end{pmatrix} C(kn)=C(k1n1)+C(k1n),可以用动态规划,使用二维数组来表示组合数,求组合数矩阵。arr[i][j]表示从i中取j的种数,即 C ( i , j ) C(i,j) C(i,j)
  2. 需要int、long数值范围,和数据溢出导致不能AC。 对于C++需要使用long long,long依赖于系统是32位还是64位的版本

C/C++ 版

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;

#define mod 1000000007

// C++中必须使用long long 类型,才不会溢出
long long arr[101][101];

//TODO:组合数初始化
void initialArr() 
{
	arr[0][0] = 1;
	for (int i = 1; i <= 100; i++) 
	{
		arr[i][0] = 1;
		for (int j = 1; j <= i; j++)
		{
			arr[i][j] = (arr[i - 1][j - 1] + arr[i - 1][j]) % mod;		// 组合数C(n, k) = C(n-1, k-1) + C(n-1, k)
		}
	}
}

// 计算结果
long long calculateApproach(int targetLen, int firstLen, int firstNum, int secondLen, int secondNum)
{
	long long res = 0;
	for (int i = 0; i <= firstNum && firstLen * i <= targetLen; i++) // 当targetLen能由i首firstLen歌曲和 (targetLen - firstLen * i) / secondLen首secondLen歌曲组成时
	{
		if ((targetLen - firstLen * i) % secondLen == 0 && (targetLen - firstLen * i) / secondLen <= secondNum)
		{
			// 注意取模
			long long temp = (long)((arr[firstNum][i] * arr[secondNum][(targetLen - firstLen * i) / secondLen]) % mod);
			res = (res + temp) % mod;
			//res = (res + (arr[firstNum][i] * arr[secondNum][(targetLen - firstLen * i) / secondLen]) % mod) % mod;
		}
	}
	return res;
}

int main()
{
	int targetLen = 5, firstLen = 2, firstNum = 3, secondLen = 3, secondNum = 3;
	// scanf和printf 比cin和cout的效率更高
	scanf("%d", &targetLen);
	scanf("%d%d%d%d", &firstLen, &firstNum, &secondLen, &secondNum);

	// cin >> targetLen;
	// cin >> firstLen >> firstNum >> secondLen >> secondNum;

	initialArr();
	long long res = calculateApproach(targetLen, firstLen, firstNum, secondLen, secondNum);

	printf("%d\n", res);
	system("pause");
    return 0;
}


java版

import java.io.IOException;
import java.util.Scanner;

/**
 * Time: 2019-01-06    Author: snowy
 * 腾讯2018秋招笔试题1: 小Q的歌单
 * 【题目描述】 小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取模的结果.
 * */

public class SongList {

    long mod = 1000000007;
    public static long[][] arr = new long[101][101];

    // 用来计算组合数的
    public void initialArr(long[][] arr) {
        arr[0][0] = 1;
        for (int i = 1; i < 101; i ++) {
            arr[i][0] = 1;
            for (int j = 1; j <= i; j ++) {
                arr[i][j] = (arr[i - 1][j - 1] + arr[i - 1][j]) % mod;        // 计算组合数: C(n, k) = C(n-1, k-1) + C(n-1, k)
            }
        }
    }

    public int caculateApproach(int targentLen, int firstLen, int firstNum, int secondeLen, int secondNum) {
        int res = 0;
        for (int i = 0; i < firstNum; i ++) {
            if (firstLen * i <= targentLen && (targentLen - firstLen * i) % secondeLen == 0 && (targentLen - firstLen * i) / secondeLen <= secondNum) {
         		// 注意取模
                //res += (int)((long)(arr[firstNum][i] * arr[secondNum][(targentLen - firstLen * i) / secondeLen]) % mod);
                long temp = (long)(arr[firstNum][i] * arr[secondNum][(targentLen - firstLen * i) / secondeLen]);
                res = (int)((res + temp)%mod);
            }
        }
        return res;
    }

    public static void main(String[] args) {
        SongList sl = new SongList();
        System.out.println("pleace input the test example:");
        Scanner input = new Scanner(System.in);
        int targetLen = input.nextInt();
        int firstLen = input.nextInt();
        int firstNum = input.nextInt();
        int secondLen = input.nextInt();
        int secondNum = input.nextInt();
        sl.initialArr(arr);
        int ans = sl.caculateApproach(targetLen, firstLen, firstNum, secondLen, secondNum);
        System.out.println(ans);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值