问题
假设一共有N对新婚夫妇,其中有M个新郎找错了新娘,求发生这种情况一共有多少种可能.
Input
输入数据的第一行是一个整数C,表示测试实例的个数,然后是C行数据,每行包含两个整数N和M(1<M<=N<=20)。
Output
对于每个测试实例,请输出一共有多少种发生这种情况的可能,每个实例的输出占一行。
Sample Input
2
2 2
3 2
Sample Output
1
3
分析:
这个题有两层含义,n对夫妇中选m对,m对夫妇进行错排
错排:每队夫妇都和伴侣匹配,错排的目的是使每组都匹配到错的人。
f(n,m) = Cn_m * 错排(m)
错排:f(n)
f(n) = (n-1) * f(n-1):前面n-1个人已经形成了错排,有f(n-1)中可能,那么第n个人只要和前n-1中任一交换即可
f(n) = (n-1) * f(n-2):前面n-1个人没有错排好,要和第n个人一起形成整体错排则前n-1个人里只允许一对夫妇是正确的,
则问题变成从n-1个人选一个(n-1中情况)用来和第n个人交换,剩下n-2个人错排,即(n-1)*f(n-2)
f(n) = (n-1) * (f(n-1) + f(n-2))
最后想了一下出发点,应该是讨论f(n-1)已知和f(n-1)未知,从这里切入。
代码
import java.util.Scanner;
public class Main {
private static long[][] C_n_m = new long[21][21];
private static long[] A_m = new long[21];
private static void init() {
A_m[0] = 0;
A_m[1] = 0;
A_m[2] = 1;
for(int i = 0 ; i <= 20 ; i++) {//组合
for(int j = 0; j <= i ; j++) {
if(j==0)
C_n_m[i][j] = 1;
else
C_n_m[i][j] = C_n_m[i-1][j-1] + C_n_m[i-1][j];
}
}
for(int i = 3 ; i <= 20 ; i ++) {//错排
A_m[i] = (A_m[i-1]+A_m[i-2])*(i-1);
}
}
public static void main(String[] args) {
init();
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
int[][] in = new int[num][2];
for(int i = 0; i < num ; i++) {
in[i][0] = sc.nextInt();
in[i][1] = sc.nextInt();
}
for(int i = 0; i < num ; i++) {
System.out.println(C_n_m[in[i][0]][in[i][1]]*A_m[in[i][1]]);
}
sc.close();
}
}
当然用Cnm的数学公式,阶乘得到组合数也行,但明显没有递推简单
import java.util.Scanner;
class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[][] in = new int[n][2];
long[] res = new long[20];
long[] cuopai = new long[21];
cuopai[0] = 0;
cuopai[1] = 0;
cuopai[2] = 1;
for(int i = 3 ; i <= 20 ;i ++) {
cuopai[i] = (i - 1) * (cuopai[i-1] + cuopai[i -2]);
}
//输入并保存数据
for(int i = 0; i < n ; i++) {
in[i][0] = sc.nextInt();
in[i][1] = sc.nextInt();
}
for(int i = 0; i < n ; i++) {
if(in[i][0] == in[i][1] || in[i][0] < in[i][1])
res[i] = 1* cuopai[in[i][1]];
else {
long fenmu = jiecheng(in[i][1]);
long fenzi = jiecheng(in[i][0])/jiecheng(in[i][0] - in[i][1]);
long C_n_m = fenzi / fenmu;
res[i] = C_n_m * cuopai[in[i][1]];
}
}
for(int i = 0; i < n ; i++) {
System.out.println(res[i]);
}
}
private static long jiecheng(int x) {
if(x==0 || x==1)
return 1;
long res = 1;
for(int i = x ; i > 0 ; i--) {
res *= i;
}
return res;
}
}
同时记录下提交的各个版本做分析总结
- version1.0:
只看到了第一层,做了个组合公式的java描述
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[][] in = new int[n][2];
long[] res = new long[20];
for(int i = 0; i < n ; i++) {
in[i][0] = sc.nextInt();
in[i][1] = sc.nextInt();
}
for(int i = 0; i < n ; i++) {
if(in[i][0] == in[i][1])
res[i] = 1;
else {
long fenmu = jiecheng(in[i][0]);
long fenzi = jiecheng(in[i][1])/jiecheng(in[i][1] - in[i][0]);
res[i] = fenzi / fenmu;
}
}
for(int i = 0; i < n ; i++) {
System.out.println(res[i]);
}
}
private static long jiecheng(int x) {
long res = 1;
for(int i = x ; i > 0 ; i--) {
res *= i;
}
return res;
}
}
- version2.0:
原来还有第二层,那该吧,此时还没有注意到边界,结果提交后还是wa,答案错误,以为是算法问题,那我改成递推试试,得到version3
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[][] in = new int[n][2];
long[] res = new long[20];
long[] cuopai = new long[20];
cuopai[0] = 0;
cuopai[1] = 0;
cuopai[2] = 1;
for(int i = 3 ; i < 20 ;i ++) {
cuopai[i] = (i - 1) * (cuopai[i-1] + cuopai[i -2]);
}
//输入并保存数据
for(int i = 0; i < n ; i++) {
in[i][0] = sc.nextInt();
in[i][1] = sc.nextInt();
}
for(int i = 0; i < n ; i++) {
if(in[i][0] == in[i][1] || in[i][0] < in[i][1])
res[i] = 1;
else {
long fenmu = jiecheng(in[i][1]);
long fenzi = jiecheng(in[i][0])/jiecheng(in[i][0] - in[i][1]);
long C_n_m = fenzi / fenmu;
res[i] = C_n_m * cuopai[in[i][1]];
}
}
for(int i = 0; i < n ; i++) {
System.out.println(res[i]);
}
}
private static long jiecheng(int x) {
if(x==0 || x==1)
return 1;
long res = 1;
for(int i = x ; i > 0 ; i--) {
res *= i;
}
return res;
}
}
- version3.0
此时还是没有注意到边界问题,提交后还是wa!但递推用在处理此题上明显比我的version2.0要更加直接更加高效更加优雅。
public static void main(String[] args) {
init();
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
int[][] in = new int[num][2];
for(int i = 0; i < num ; i++) {
in[i][0] = sc.nextInt();
in[i][1] = sc.nextInt();
}
for(int i = 0; i < num ; i++) {
System.out.println(C_n_m[in[i][0]][in[i][1]]*A_m[in[i][1]]);
}
sc.close();
}
private static long[][] C_n_m = new long[20][20];!!!注意边界1<m<=n<=20
private static long[] A_m = new long[20];!!!注意边界
private static void init() {
A_m[0] = 0;
A_m[1] = 0;
A_m[2] = 1;
for(int i = 0 ; i < 20 ; i++) {//组合
for(int j = 0; j <= i ; j++) {
if(j==0)
C_n_m[i][j] = 1;
else
C_n_m[i][j] = C_n_m[i-1][j-1] + C_n_m[i-1][j];
}
}
for(int i = 3 ; i < 20 ; i ++) {//错排
A_m[i] = (A_m[i-1]+A_m[i-2])*(i-1);
}
}
经验
- 注意边界!注意边界!注意边界
- 递推是真好用,递推问题关键是抽象出公式来,结合我做的几道其他题来看,一般将问题分成两个部分,如前n-1个和最后一个,要么讨论前n-1个,要么从第n个反推,即讨论n的情况反过来推前n-1项。