【秋招机试真题】荣耀机试0916-3-可重复选元素组合问题

/*
*   题目描述:
*       dota游戏里面,召唤师可以控制冰雷火三种元素,并通过元素组合
*   产生新的技能。现在我们修改了一张新的地图,地图中他能够控制n种元素,
*   并且将m个元素围成一个圈组成一个新技能(这m个元素通过旋转或翻转,算
    作重复,如123、231、312、321、213、132都算重复),那么召唤师能组合
    多少级能(20000>=n>=1, 1<=m<=10000),由于结果可能很大,请将结果对
    1000000007取余
    
    输入描述:
        n和m
    输出描述:
        组合的技能数

    示例1:
        输入: 
            3 3
        输出:
            10
        说明:
            111、112、113、122、123、133、222、223、233、333一共10种
*/


说明:这道题有多种解法,一开始我是按照全排列的回溯方法来做,超时,后来我又试了结合数学计算的递归方法,最后将递归变为从底至上的方法可以解决超时问题


方法1:全排列的回溯方法

该方法适合返回组合的数组,但是对于这种返回个数的题,时间复杂度太高

 代码如下:

#include<iostream>
#include<vector>
using namespace std;
int res = 0;
void back_tracking(int n, int m, int level, int start, vector<int>& tmp) {
    //cout << start << " ";
    if (level == m) {
        res++;
        /*
        for (int i = 0;i < tmp.size(); ++i) {
            if (i == 0) cout << tmp[0];
            else cout << " " << tmp[i];
        }
        cout << endl;
        */
        return;
    }
    for (int i = start; i <= n; ++i) {
        tmp.push_back(i);
        back_tracking(n, m, level + 1, i, tmp);
        tmp.pop_back();
    }
}
int solution(int n, int m) {
    vector<int> tmp = {};
    back_tracking(n, m, 0, 1, tmp);
    return res;
}
int main() {
    // 共有n个元素, m个元素组成新技能
    int n, m;
    cin >> n >> m;
    cout << solution(n, m) << endl;
}

方法2:数学计算的DP方法

参考其他博主的链接:有n个数,从中取m个数,可以重复取,有多少种组合

- 数学方法1:分类讨论:

 - 数学方法2:

        一共有n个数,假设第i个数出现了Xi次,也就是X1+X2+...+Xi+...+Xn=m。所有元素的个数和为m。该问题转化为求有多少个不同的个数和为m的非负整数解,转化成了将n个小球放进m个箱子里的问题()C(m+n-1, m)

参考链接:可重复选元素的组合问题(21)
动态规划具体思路:

1、初始化:定义dp[n+1][m+1]大小的数组,dp[i][j]代表了从i个元素中选取j个元素的组合数;

        当i=0,j=0时,dp[0][0] =1;

        当i=0, j不等于0时,dp[0][j] = 0,代表从0个元素中选取非0个元素的组合数为0

        当i不等于0,j=0时,dp[i][0] = 1,代表从非0个元素中选取0个元素的组合数为1

2、状态转移方程:

dp[1][1] = 1, 有组合1

dp[1][2] = 1,有组合11

dp[2][1] = 2, 有组合1, 2

dp[2][2] = 3,有组合11,22,12

...

可发现规律:

dp[i][j] = dp[i-1][j]+dp[i][j-1]

(我目前无法从理解的角度给出解释)

 代码如下:

#include<iostream>
#include<vector>
using namespace std;
int solution(int n, int m) {
	vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
	for (int i = 0; i <= n; ++i) {
		dp[i][0] = 1;
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
		}
	}
	return dp[n][m];
}
int main() {
	int n, m;
	cin >> n >> m;
	cout << solution(n, m) << endl;
}

可以进行空间压缩:n*m->m

代码如下:

int solution_1(int n, int m) {
	vector<int> dp(m + 1, 0);
	dp[0] = 1;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			dp[j] = dp[j] + dp[j - 1];
		}
	}
	return dp[m];
}
int main() {
	int n, m;
	cin >> n >> m;
	//cout << solution(n, m) << endl;
	cout << solution_1(n, m) << endl;
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值