Java中的排列组合
- 排列组合
- 组合
-
- 杨辉三角预处理组合数
- [例题1(FROM 洛谷 P2822)](#例题1(FROM 洛谷 P2822))
- 排列
排列组合
这个概念高中已经学过了,排列(A)就是不看顺序,比如2,1和1,2算一种情况。而组合(C)就是看顺序,上述例子算两种情况。下面给出排列和组合的数学公式:
- A (nm ) = n! / m!【排列】
- C (nm) = n! / (m! * (n-m)!)【组合】
组合
杨辉三角预处理组合数
- 杨辉三角的基本性质:
第n行第m列的值等于组合数(n m)
边界条件:(n0) = (nn) = 1
递推关系:(nm) = (n-1m)+(n-1m-1) - 基础预处理
java
// 初始化
int[][] C = new int[MAX][MAX];
for (int i = 0; i < MAX; i++) {
C[i][0] = 1;
C[i][i] = 1;
}
// 递推填充
for (int i = 2; i < MAX; i++) {
for (int j = 1; j < i; j++) {
C[i][j] = C[i-1][j] + C[i-1][j-1];
}
}
例题1(FROM 洛谷 P2822)

代码实现
java
import java.util.*;
public class Main{
public static void main(String [] args){
Scanner input = new Scanner (System.in);
int t = input.nextInt();
int k = input.nextInt();
int MAX = 2001;//题没截取全,最后写了,数据最大会是2000,所以开2001
int [][] c = new int [MAX+1][MAX+1];
int [][] ans = new int [MAX+1][MAX+1];
//------------------------------------------------基础预处理------------------------------------------------------------------------------------------------------------------
for(int i=0;i<MAX;i++){
c[i][0] = 1 % k;//如果直接写1,当k=1时,1%1=0
c[i][i] = 1 % k;
}
for(int i=2;i<=MAX;i++)
for(int j=1;j<=i;j++)
c[i][j] = (c[i-1][j] + c[i-1][j-1]) % k;
//---------------------------------------------------前缀和处理---------------------------------------------------------------------------------------------------------------
int [][] rowPre = new int [MAX+1][MAX+1];
for(int i=0;i<=MAX;i++)
for(int j=0;j<=MAX;j++){
rowPre[i][j] = (j == 0)?0:rowPre[i][j-1];
if(j <= i && c[i][j] == 0)
rowPre[i][j] ++;
}
//---------------------------------------------------答案数组处理---------------------------------------------------------------------------------------------------------------
for(int j = 0;j<=MAX;j++)
for(int i=0;i<=MAX;i++){
if(i == 0)
ans[i][j] = rowPre[i][j];
else ans[i][j] = ans[i-1][j] + rowPre[i][j];
}
//---------------------------------------------------输出处理------------------------------------------------------------------------------------------------------------------------------
for(int i = 0;i<t;i++){
int n = input.nextInt();
int m = input.nextInt();
System.out.println(ans[n][m]);
}
}
}
排列
在排列中,涉及到许多阶乘,阶乘的循环处理太过耗费时间,所以我们可以使用阶乘数组 来预处理。同时在计算排列数的时候,如果我们需要取模,就不可以单纯的使用除法,因为模运算中没有除法的分配律,这时候我们就要用到阶乘逆元数组。
阶乘数组
java
static long [] fac = new long [MAX];
fac[0] = 1;//规定 0!= 1
for(int i=1;i<MAX;i++)
fac[i] = fac[i-1] * i;
阶乘逆元数组
逆元类似于数学中的倒数,阶乘的逆元数组通常在模运算中使用,比如模数为7,则3 的逆元是 5,因为 3 * 5 = 15 ,15 % 7 = 1 ,如果存在b可以使(a*b)% mod = 1,则a与b互为逆元。
- 小费马定理
invFac[MAXN-1] = fac[MAXN-1]^(MOD-2) % MOD
invfac[i] = (invfac[i+1]*(i+1))
java
static long [] invfac = new long [MAX];
invfac[MAX-1] = pow(fac[MAX-1],MOD-2);//pow是自定义快速幂函数
for(int i=MAX-2;i>=0;i--)
invfac[i] = (invfac[i+1]*(i+1)) % MOD;
例题1(FROM 洛谷 P4071)
有个问题非常棘手,由于Java的运行内存比C++大的多,所以我拼尽全力优化了所有也无法全部AC,总会有MLE。(AI也没招了......)大家如果有可以全部AC的方法可以发出来,我将逐字学习!

思路
在这道题中要求有多少种方式,我们从数学角度先将其建模。首先"满足恰好有m个位置使得 ai = i",所以我们要从n个数里取出m个数固定,这个取法有C(nm)种。然后我们排列 其他未固定的数字,值得注意的是:未固定数字不能再放在其原本的位置上 ,于是我们要用到错排。
- 错排
定义:n个数排列且所有数字都不在原有位置上的情况数
初始化:D[0] = 1空排列视为合法错排,D[1] = 0只有一个元素无法错排
递推公式:D(n) = (n-1) * ( D(n-1) + D(n-2) )
代码实现
java
import java.util.*;
import java.io.*;
public class Main{
static BufferedReader bf = new BufferedReader (new InputStreamReader (System.in));
static StringTokenizer st;
static int MOD = 1000000000 + 7;
static int MAX = 1000000 + 1;
static long [] fac = new long [MAX];
static long [] invfac = new long [MAX];
static long [] D = new long [MAX];
public static void main(String [] args)throws IOException{
init();
int T = Integer.parseInt(next());
StringBuilder sb = new StringBuilder();
while(T>0){
int n = Integer.parseInt(next());
int m = Integer.parseInt(next());
if(m>n){
System.out.print(0);
continue;
}
long c = C(n,m);
long d = D[n-m];
long ans = (c*d)%MOD;
sb.append(ans).append("\n");
T--;
}
System.out.print(sb.toString());
}
static void init(){
fac[0] = 1;
for(int i=1;i<MAX;i++)
fac[i] = (fac[i-1] * i) % MOD;
// 预处理阶乘逆元 - 使用费马小定理
// invFac[MAXN-1] = fac[MAXN-1]^(MOD-2) % MOD
invfac[MAX-1] = pow(fac[MAX-1],MOD-2);
for(int i=MAX-2;i>=0;i--)
invfac[i] = (invfac[i+1]*(i+1)) % MOD;
D[0] = 1;
D[1] = 0;
for(int i=2;i<MAX;i++)
D[i] = ((long)(i-1)*(D[i-1]+D[i-2]))%MOD;
}
static long C(int n,int m){
if(m>n || m<0) return 0;
return ((fac[n] * invfac[m])%MOD * invfac[n-m]) % MOD;
}
static long pow(long a,long b){
long res = 1;
while(b > 0){
if( (b&1) == 1) {
res *= a;
res %= MOD;
}
a *= a;
a %= MOD;
b>>=1;
}
return res;
}
static String next() throws IOException{
while(st == null||!st.hasMoreTokens()){
String str = bf.readLine();
if(str == null) return null;
st = new StringTokenizer(str);
}
return st.nextToken();
}
}