求排列组合的两种方式 模板

博客介绍了排列组合的基本概念,p(n,m)表示从n个不同元素中取m个元素按次序排列的排列数,C(n,m)表示不考虑次序的组合数。还介绍了求C(n,m)的两种方式,一是连乘m个整数商,二是利用二项式系数公式,后者更适用于多次询问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(图片都来源百度百科)

先说p(n,m),表示从n个不同元素中取出m个元素,并按次序排列的排列数,即字典序,组成的排列叫全排列。

 若从n个不同元素中取出m个元素,不考虑次序,则称从n中取出m个组合,其组合数表示为C(n,m)=n!/m!*(n-m)!;

关于排列组合的性质用途太多了,下次再统一整理,这里只介绍求C(n,m)的两种方式


第一种:连乘m个整数商:

\small C(n,m)=\frac{(n-m+1)*(n-m+2)*....*n}{1*2*....*m}=\frac{n-m+1}{m}*\frac{n-m+2}{m-1}*....*\frac{n}{1}

 对于m个连续的自然数(n-m+1),(n-m+2),...n,必定有一个数能被m整除,也必定能有一个被m-1整除,因此,在运算过程中,按分母从大到小,及时的进行分子分母相除运算;然后连成m个整商。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll work(int n,int m){
	if(m>n/2) m = n-m;
	ll a = 1,b = 1;
	for(int i=1;i<=m;i++){
		a*=n+1-i;
		b*=i;
		if(a%b==0) a/=b,b=1;
	}
	return a/b;
}
int main(){
	int n,m;
	scanf("%d %d",&n,&m);//49 6 
	printf("%lld\n",work(n,m));//13983816 
	return 0;
} 

 方法二:利用二项式系数公式:

\small C(n,m)=C(i-1,j)+C(i-1,j-1)

即c[i][0]=1,并且c[i][j] = c[i-1][j] + c[i-1][j-1]。

这个方法更加适用于多次询问,先离线计算出要问的范围,再直接查询问的n,m;

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll c[105][105];
void pp(){
	for(int i=0;i<102;i++) c[i][0]=1;
	
	for(int i=1;i<101;i++)
	for(int j=1;j<101;j++)
	c[i][j]=c[i-1][j] + c[i-1][j-1];
}
int main(){
    pp();
    int n,m;
    while(~scanf("%d %d",&n,&m) && (n||m)){//100 6
    	printf("C(%d,%d) = %lld",n,m,c[n][m]);//1192052400
	}
	return 0;
} 

 

### 关于C语言实现排列组合算法的代码模板 #### 1. 全排列算法概述 全排列是指将给定的一组元素按照不同的顺序重新排列。对于长度为N的不同元素集合,其所有不同排列的数量为 N! 。为了生成这些排列,通常采用递归方法或迭代方法。 #### 2. 使用递归法实现全排列 下面展示了一个基于递归方式来构建数组`source[]`内元素的所有可能排列的函数: ```c #include <stdio.h> // 打印当前排列结果 void disp(int *arr, int n) { for (int i = 0; i < n; ++i) printf("%d ", arr[i]); printf("\n"); } // 交换两个数的位置 void swap(int* a, int* b){ int temp=*a; *a=*b; *b=temp; } // 递归调用以获得全部排列 void FullPermutation(int source[], int begin, int end) { if (begin == end){ // 当只剩下一个位置时打印该序列并返回上一层 disp(source,end); }else{ for (int i=begin;i<=end;++i){ swap(&source[begin],&source[i]); // 将第i个字符放到首位 FullPermutation(source,begin+1,end); // 对剩余部分继续解 swap(&source[begin],&source[i]); // 恢复原状以便下一轮循环 } } } ``` 此段代码通过不断交换起始位置与其他各位置上的数值,并对剩下的未处理过的子列表重复这一过程直到遍历结束[^2]。 #### 3. 组合算法简介 不同于排列关注的是对象之间的相对次序变化,而组合则更关心选取哪些成员而不考虑它们的具体安排。因此,在编写组合生成器时,重点在于如何挑选出特定数量的对象而不是改变现有对象间的先后关系。 由于提供的参考资料主要集中在全排列方面,这里提供一种简单的非递减顺序下的组合生成逻辑作为补充说明: ```c void Combination(int set[], int subsetSize, int setSize, int index, int currentSetIndex, bool used[]) { if (index == subsetSize) { // 如果已经选够了指定数目,则输出这组数据 for (int j = 0; j < subsetSize; j++) { for (int k = 0; k < setSize; k++) if (used[k]) printf("%d ", set[k]); } putchar('\n'); return; } if (currentSetIndex >= setSize) return; // 不取当前位置元素的情况 Combination(set, subsetSize, setSize, index, currentSetIndex + 1, used); // 取当前位置元素的情况 used[currentSetIndex] = true; Combination(set, subsetSize, setSize, index + 1, currentSetIndex + 1, used); used[currentSetIndex] = false; } ``` 这段代码展示了如何利用递归来枚举从一组固定大小的数据集中选出一定量元素的所有可能性。注意这里的输入参数包括原始集合、所需子集规模以及辅助变量用于跟踪进度和状态标记[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值