1 题目
(此题来源于牛客网腾讯2018秋招笔试真题)
1.1 问题描述
小q有x首长为A的不同的歌和长为y首长度为B的不同的歌,从种选取任意首,组成长度为k的歌单,每首歌只能被选取一次,不考虑歌单内歌曲的先后顺序,问有多少种组成方法。
1.2 输入描述
第一行是一个整数,表示歌单的总长度为K。
第二行有四个整数,A(<=10)、X(<=100)、B(<=10)、Y(<=100),分别代表第一种长度和数量,第二种长度和数量,保证A!=B。
1.3 输出描述
输出为一个整数,表示组成方式有多少种,若输出结果过大,则对该数进行1000000007(中间8个0)取模的结果作为最终结果。
1.4 输入示例
5
2 3 3 3
1.5 输出示例
9
2 问题分析
第一种歌的长度为A,一共有X首,假设从里面选区m首,则有C1(m,X)种选法,同理第二种歌的长度为B,一共有Y首,假设从里面选区n首,则一共有C2(n,Y)种选法,然后将C1和C2进行相乘,结果则为最终结果(若太大则进行取模)。
这是一道典型的排列组合问题中的排列问题,如果不考虑计算量和超出整型范围问题,则直接可以通过公式计算得出最终的结果。但是,假设第一种歌有100首,从里面取出50首,进行组合计算则是C(50,100),这个数明显太大。
对数据进行分析,若为1首歌,有选0首和选1首两种状态;若为2首歌,有选0首、选1首、选2首三种状态,对应的种类分别为1 2 1;若为3首歌,有选0首、1首、2首、3首,对应的种类为1 3 3 1,以此类推。。。观察种类组成结构:
1 1
1 2 1
1 3 3 1
1 4 6 4 1
.。。。
若将最上面一排按上1,则刚好满足杨辉三角,第n行(从第0行开始)的第m个数刚好是C(m,n),C(m,n)刚好等于左上角C(m-1,n-1)和正上方C(m,n-1),第一列除外,第一列全部为1。若用二维数组表示则c[n][m] = c[n-1][m-1] + c[n-1][m]。这种方法增加了一定的空间消耗,大大降低了时间复杂度和计算复杂度。
3 代码编写
#include <iostream>
#include <stdio.h>
using namespace std;
int** init(const int len) {
int** brr = new int*[len];
for (int i = 0; i < len; i++)
brr[i] = new int[len];
//for (int i = 0; i < len; i++)
// for (int j = 0; j < len; j++)
// brr[i][j] = 0; //将数组初始化为0
brr[0][0] = 1;
brr[0][1] = 0;
//要从brr[1][1]开始计算,而第一行第2个数用到brr[0][0]和brr[0][1]
for (int i = 1; i < len; i++) {
brr[i][0] = 1; //每一行的第一个元素都初始化为1
for (int j = 1; j < len; j++) {
brr[i][j] = brr[i - 1][j - 1] + brr[i - 1][j];
//第i行第j列个元素等于左上角加上正上方元素
}
}
return brr;
}
int getmod(int a) { //取模函数
const int mod = 1000000007; //mod
while (a > mod)
a -= mod;
return a;
}
void test() {
int K; //组成歌单的总长度
int A, X, B, Y; //第1种歌的长度和种类,第2种歌的长度和种类
long long ans = 0;
//_CRT_SECURE_NO_WARNINGS
scanf("%d", &K);
scanf("%d%d%d%d", &A, &X, &B, &Y);
int max = (A > B ? A : B) + 2; //二维数组的行列数,防止越界多申请一个
int** arr = init(max); //初始化杨辉三角数组
for (int i = 0; i <= X; i++) {
for (int j = 0; j <= Y; j++) {
if (i * A + j * B == K) { //若i首第一种歌和j首第二种歌长度加起来为K,则满足要求
ans = getmod((ans + getmod(arr[X][i] * arr[Y][j]))); //防止数过大进行取模
}
}
}
printf("%lld\n", ans);
}
int main()
{
test();
return 0;
}