组合数学--排列组合

1. 概述

组合数学这是笔者在研究生阶段唯一的一门数学课了吧,希望做个了断。
组合数学可以理解成是离散数学中的一部分,广义的组合数学就是离散数学
离散数学可以理解成是狭义的组合数学和图论、代数结构、数理逻辑的统称
以上所说仅仅是叫法上的不同,总而言之组合数学是研究离散对象的科学,但是在计算机科学中有着重要的作用

1.1 应用

这里只提几个很出名的问题

  • 幻方问题
  • 四色问题
  • 最短网络
  • 最小生成树和最小斯坦纳树
  • 无尺度网络
  • 小世界网络

1.2 三大问题

  • 存在(Existence Problem)
  • 计数(Counting Problem)
  • 优化(Optimization Problem)

2. 排列组合

最基本的排列组合无需多言
计数问题最重要的是做到无重复无遗漏,即不重不漏

2.1 两大法则

  • 加法法则:分类问题
  • 乘法法则:分步问题

2.2 排列

  • 圆排列
    P ( n , r ) r , 2 ≤ r ≤ n \frac {P(n, r)} r, 2 \leq r \leq n rP(n,r),2rn
  • 项链排列
    P ( n , r ) r , 3 ≤ r ≤ n \frac {P(n,r)} r, 3 \leq r \leq n rP(n,r),3rn
  • 多重全排列
    • t种球,个数有限
    • 打标号
  • 多重全排列
    • 分类枚举
  • 可重排列
    n r n^r nr

3. 放球模型

  • 排列:n个不同的球中取r个,放入r个不同的盒子,每个盒子一个
    P ( n , r ) = n ∗ ( n − 1 ) ∗ . . . ∗ ( n − r + 1 ) = n ! ( n − r ) ! P(n, r) = n*(n-1)*...*(n-r+1)=\frac {n!} {(n-r)!} P(n,r)=n(n1)...(nr+1)=(nr)!n!
  • 组合:n个不同的球中取r个,放入r个相同的盒子,每个盒子一个
    C ( n , r ) = n ! r ! ( n − r ) ! C(n, r) = \frac {n!} {r!(n-r)!} C(n,r)=r!(nr)!n!

4. 模型转换

A事件不好计算,但是A和B一一对应,B好计算事件,可以转换为求B的,也就求出来了A的

100 100 100人打乒乓球赛,每个选手至少打一局,输者淘汰,最后产生一名冠军,需要比赛多少场?
答: 99 99 99场,因为要选出来冠军,淘汰的选手和比赛一一对应
Cayley定理:n个有标号的顶点的树的数目是 n n − 2 n^{n-2} nn2,用 n − 1 n-1 n1条边将 1 , 2 , . . . , n 1,2,...,n 1,2,...,n连接起来的连通图的数目是 n n − 2 n^{n-2} nn2

5. 线性方程的解

线性方程 x 1 + x 2 + . . . + x n = b x_1+x_2+...+x_n = b x1+x2+...+xn=b的非负整数解的个数是 C ( n + b − 1 , b ) C(n+b-1, b) C(n+b1,b)
【解释】相当于把一堆x分成b组,每组个数不限,

5.1 若干等式及其组合意义

  1. ( 0 , 0 ) (0,0) (0,0)走到 ( m , n ) (m,n) (m,n)的方法数 C ( m + n , m ) C(m+n, m) C(m+n,m)
  2. C ( n , r ) = C ( n , n − r ) C(n, r) = C(n, n-r) C(n,r)=C(n,nr)
  3. C ( n , r ) = C ( n − 1 , r ) + C ( n − 1 , r − 1 ) C(n, r) = C(n-1, r) + C(n-1, r-1) C(n,r)=C(n1,r)+C(n1,r1)
    1 ≤ a 1 ≤ a 2 ≤ . . . ≤ a n ≤ n 1\leq a_1 \leq a_2 \leq ... \leq a_n \leq n 1a1a2...ann取整数,对取法分类
    - a 1 = 1 a_1 = 1 a1=1,有 C ( n − 1 , r − 1 ) C(n-1, r-1) C(n1,r1)种方案
    - a 1 > 1 a_1 \gt 1 a1>1,有 C ( n − 1 , r ) C(n-1, r) C(n1,r)种方案
  4. C ( n + r + 1 , r ) = C ( n + r , r ) + C ( n + r − 1 , r − 1 ) + . . . + C ( n + 1 , 1 ) + C ( n , 0 ) C(n+r+1, r) = C(n+r, r) + C(n+r-1, r-1) + ... + C(n+1, 1) + C(n, 0) C(n+r+1,r)=C(n+r,r)+C(n+r1,r1)+...+C(n+1,1)+C(n,0)
  5. C ( n , l ) C ( l , r ) = C ( n , r ) C ( n − r , l − r ) C(n,l) C(l, r) = C(n, r)C(n-r, l-r) C(n,l)C(l,r)=C(n,r)C(nr,lr)
  6. C ( m , 0 ) + C ( m , 1 ) + . . . + C ( m , m ) = 2 m , m ≥ 0 C(m, 0) + C(m, 1) +...+C(m, m) = 2^m, m \geq 0 C(m,0)+C(m,1)+...+C(m,m)=2m,m0
  7. C ( n , 0 ) − C ( n , 1 ) + C ( n , 2 ) − . . . ± C ( n , n ) = 0 C(n,0) - C(n, 1)+C(n,2)-...\pm C(n,n) = 0 C(n,0)C(n,1)+C(n,2)...±C(n,n)=0
    • 6 和 7 都是 ( x + y ) n (x+y)^n (x+y)n
  8. Vandemonde 恒等式 C ( m + n , r ) = C ( m , 0 ) C ( n , r ) + C ( m , 1 ) C ( n , r − 1 ) + . . . + C ( m , r ) C ( n , 0 ) C(m+n, r)=C(m,0)C(n,r)+C(m,1)C(n, r-1)+...+C(m, r)C(n, 0) C(m+n,r)=C(m,0)C(n,r)+C(m,1)C(n,r1)+...+C(m,r)C(n,0)

6. 全排列生成算法

这算是本章的重点了吧
全排列的个数是 n ! n! n!。现在的问题是

  1. 生成所有的排列,
  2. 根据某一个排列,计算之后或者之前第 k k k个排列是什么。

注意一点,因为是全排列,所以其含义包含着所有元素都不相同。

6.1 字典序法

经典的方法,就是按照从小到大枚举变化即可。
举例来说, 1234 1234 1234
1234 , 1243 , 1324 , 1342 , 1423 , 1432 2134 , 2143 , 2314 , 2341 , 2413 , 2431 3124 , 3142 , 3214 , 3241 , 3412 , 3421 4123 , 4132 , 4213 , 4231 , 4312 , 4321 1234, 1243, 1324, 1342, 1423, 1432\\ 2134, 2143, 2314, 2341, 2413, 2431\\ 3124, 3142, 3214, 3241, 3412, 3421\\ 4123, 4132, 4213, 4231, 4312, 4321\\ 1234,1243,1324,1342,1423,14322134,2143,2314,2341,2413,24313124,3142,3214,3241,3412,34214123,4132,4213,4231,4312,4321
可以假设初始有一个向左的箭头,代表 k k k移动的方向,但是 k k k是否可以移动取决于在 k k k的方向上是否一个比 k k k小的数字存在。
字典序法想要的是这一个和下一个具有尽可能共同前缀,也即变化在尽可能后缀上。
在实际上理解的时候,从小到大的排列,就是把当前排列从右往左扫描,找到第一个下降的数字,并且把该数字和其后数字中比它大的最小的那一个交换,并把新的后续数字从小到大排列。例如: 132 132 132 3 3 3 2 2 2是上升的, 1 1 1 3 3 3是下降的,所以 1 1 1就是要交换的那一个数字,把它和 23 23 23中较小的 2 2 2交换,并把 13 13 13从小到大排列, 132 132 132下一个就是 213 213 213

6.1.1 序号

全排列的序号就是先于此排列的个数。

排列123132213231312321
序号012345
6.1.2 康拓展开

百度定义 X = a [ n ] ∗ ( n − 1 ) ! + a [ n − 1 ] ∗ ( n − 2 ) ! + . . . + a [ i ] ∗ ( i − 1 ) ! + . . . + a [ 1 ] ∗ 0 ! X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! X=a[n](n1)!+a[n1](n2)!+...+a[i](i1)!+...+a[1]0!,其中, a [ i ] a[i] a[i]为整数,并且 0 ≤ a [ i ] < i , ( 1 ≤ i ≤ n ) 0\leq a[i]\lt i, (1 \leq i \leq n) 0a[i]<i,(1in)

6.1.3 中介数

字典序的中介数代表的是当前数字右边比其小的数字的个数。计算当前排列后第 k k k个排列只要把当前中介数加上 k k k,然后再把新的中介数还原成排列数即是要求的排列数。
【注意】中介数和 k k k是不同的进制,要按照中介数的进制进行计算。
举例: 839647521 839647521 839647521,它的序号也即康拓展开式 7 ∗ 8 ! + 2 ∗ 7 ! + 6 ∗ 6 ! + 4 ∗ 5 ! + 2 ∗ 4 ! + 3 ∗ 3 ! + 2 ∗ 2 ! + 1 ∗ 1 ! 7*8!+2*7!+6*6!+4*5!+2*4!+3*3!+2*2!+1*1! 78!+27!+66!+45!+24!+33!+22!+11!,其中 72642321 72642321 72642321就是中介数, 72642321 72642321 72642321代表的是在当前数字比其右边大的数字的个数。
72642321 72642321 72642321推出 839647521 839647521 839647521
P 1 P 2 P 3 P 4 P 5 P 6 P 7 P 8 P 9 P_1 P_2 P_3 P_4 P_5 P_6 P_7 P_8 P_9 P1P2P3P4P5P6P7P8P9

  1. 7 + 1 = 8 → P 1 = 8 7+1=8→P_1 = 8 7+1=8P1=8
  2. 2 + 1 = 3 → P 2 = 3 2+1=3→P_2=3 2+1=3P2=3
  3. 6 + 1 = 7 , 但 3 < 7 已 在 P 3 左 边 出 现 , 7 + 1 = 8 , 但 8 已 在 P 3 左 边 出 现 , 8 + 1 = 9 → P 3 = 9 6+1=7,但3<7已在P_3左边出现,7+1=8,但8已在P_3左边出现,8+1=9→P_3=9 6+1=7,37P37+1=88P38+1=9P3=9
  4. 4 + 1 = 5 , 但 3 < 5 已 在 P 4 左 边 出 现 , 5 + 1 = 6 → P 4 = 6 4+1=5,但3<5已在P_4左边出现,5+1=6→P_4=6 4+1=5,3<5P45+1=6P4=6
  5. 2 + 1 = 3 , 但 3 已 在 P 5 左 边 出 现 , 3 + 1 = 4 → P 5 = 4 2+1=3,但3已在P_5左边出现,3+1=4→P_5=4 2+1=33P53+1=4P5=4
  6. 3 + 1 = 4 , 但 3 , 4 已 在 P 6 左 边 出 现 , 4 + 1 + 1 = 6 , 但 6 已 在 P 6 左 边 出 现 , 6 + 1 = 7 → P 6 = 7 3+1=4,但3,4已在P_6左边出现,4+1+1=6,但6已在P_6左边出现,6+1=7→P_6=7 3+1=43,4P64+1+1=66P66+1=7P6=7
  7. 2 + 1 = 3 , 但 3 已 在 P 7 左 边 出 现 , 3 + 1 = 4 , 但 4 已 在 P 7 左 边 出 现 , 4 + 1 = 5 → P 7 = 5 2+1=3,但3已在P_7左边出现,3+1=4,但4已在P_7左边出现,4+1=5→P_7=5 2+1=33P73+1=44P74+1=5P7=5
  8. 1 + 1 = 2 → P 8 = 2 1+1=2→P_8=2 1+1=2P8=2
  9. P 9 = 1 P_9 = 1 P9=1

中介数,序号和排列之间是一一对应的关系。
字典序下的对应关系
可用归纳法证明: ∑ k = 1 n − 1 k ∗ k ! = n ! − 1 \sum_{k=1}^{n-1}{k*k!} = n! - 1 k=1n1kk!=n!1

6.2 递增进位制

递增进位制是不固定进制中的基数,从右向左数数,第 i i i个位置的数字逢 i + 1 i+1 i+1进一位,即从右向左,逢 2 、 3 、 4 、 5 、 . . . 2、3、4、5、... 2345...进一位。
它的中介数是在 i i i的右边比 i i i小的数字的个数。但是它的中介数的进制和字典序的一致,都是从右向左,逢 2 、 3 、 4 、 5 、 . . . 2、3、4、5、... 2345...进一位。

6.3 递减进位制

递减进位制和递增进位制类似,它的中介数是把递增进位制逆置,但是它的中介数的进位是从左往右,逢 2 、 3 、 4 、 5 、 . . . 2、3、4、5、... 2345...进一位。

6.4 SJT邻位对换

它的方向是双向的,通过保存数字的“方向性“来快速得到下一个排列。
设定 b 2 , b 3 , b 4 , b 5 , b 6 , b 7 , b 8 , b 9 b_2, b_3, b_4, b_5, b_6, b_7, b_8, b_9 b2,b3,b4,b5,b6,b7,b8,b9为我们要求的中介数。

  • 规定 2 2 2的方向一定向左。 b 2 b_2 b2就是从 2 2 2开始,背向 2 2 2的方向所有比 2 2 2小的数字的个数。
  • 对于每一个比 2 2 2大的数字 i i i:
    • i i i奇数,其方向性决定于 b i − 1 b_{i-1} bi1的奇偶性,奇向右,偶向左
    • i i i偶数,其方向性决定于 b i − 1 + b i − 2 b_{i-1}+b_{i-2} bi1+bi2的奇偶性,奇向右,偶向左

b i b_i bi的值就是背向 i i i的方向直到排列边界这个区间里比 i i i小的数字的个数。
SJT方法的中介数进位同递减进位制。

6.5 总结

其实还有很多方法,老师说可以参考 D o n a l d E r v i n K n u t h Donald Ervin Knuth DonaldErvinKnuth的神书《计算机程序设计的艺术》里面Permutation Generating。
上述方法是为了让新生成的排列和原排列的尽可能相似,就是换的数字尽可能少。

7. 代码实现

并没有很好的代码格式,只是为了应付OJ,又不会使用C++,所以凑合着使用了Java。其实本次OJ中C++的long long够用。

7.1 题目描述

给定一个 1 1 1 n n n的排列 P P P,请求出这个排列根据某种顺序的后面第 k k k个排列。
输入格式

  • 第一行是三个由空格隔开的整数 n n n, t y p e type type, k k k
  • 第二行是 n n n个由空格隔开的 [ 1 , . . . , n ] [1, ..., n] [1,...,n]中的无重复整数,表示一个排列;行末可能会有空格。

t y p e type type的含义如下:

  • t y p e = 1 type = 1 type=1时,请按字典序计算;
  • t y p e = 2 type=2 type=2时,请按递增进位制计算;
  • t y p e = 3 type=3 type=3时,请按递减进位制计算;
  • t y p e = 4 type=4 type=4时,请按邻位对换法的顺序计算。

k < 0 k<0 k<0时,请计算根据顺序的前面第−k个排列。

输出格式
第一行输出 n n n个由单个空格隔开的整数,表示答案排列。
样例1
Input
9 1 1
8 3 9 6 4 7 5 2 1
Output
8 3 9 6 5 1 2 4 7
样例2
Input

9 2 1
8 3 9 6 4 7 5 2 1
Output
8 4 9 6 1 7 5 2 3
样例3
Input

9 3 1
8 3 9 6 4 7 5 2 1
Output
8 9 3 6 4 7 5 2 1
样例4
Input

9 4 1
8 3 9 6 4 7 5 2 1
Output
8 3 6 9 4 7 5 2 1
数据规模
k k k的取值保证答案存在。

存在以下几种限制:

  • 1 ≤ n ≤ 10 1≤n≤10 1n10 1 ≤ n ≤ 20 1≤n≤20 1n20
  • k > 0 k>0 k>0 k < 0 k<0 k<0
  • t y p e = 1 , 2 , 3 , 4 type=1, 2, 3, 4 type=1,2,3,4

对于每一种限制组合,都有一个测试点,共 2 × 2 × 4 = 16 2×2×4=16 2×2×4=16个测试点。

时空限制
时间限制: 1000 m s 1000ms 1000ms 内存限制: 512 M i B 512MiB 512MiB

7.2 代码实现

import java.util.Scanner;

public class ShiftingPermutations {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);

		int n = sc.nextInt();
		int type = sc.nextInt();
		long k = sc.nextLong();
		long[] nums = new long[n];

		for (int i = 0; i < n; ++i)
			nums[i] = sc.nextLong();

		switch (type) {
		case 1:
			// 请按字典序计算;
			dictOrder(nums, n, k);
			break;
		case 2:
			// 请按递增进位制计算;
			incrementalCarry(nums, n, k);
			break;
		case 3:
			// 请按递减进位制计算;
			degressiveCarry(nums, n, k);
			break;
		case 4:
			// 请按邻位对换法的顺序计算。SJT
			orthoSubstitution(nums, n, k);
			break;
		default:
			throw new UnsupportedOperationException("Unsupported Number:" + type);
		}

		printArr(nums);
	}

	// 打印数组
	private static void printArr(long[] nums) {
		for (long num : nums) {
			System.out.print(num + " ");
		}
		System.out.println();
	}

	// 原排列 -> 中介数
	private static long[] permutation2Mid(long[] nums, int n) {
		long[] midNums = new long[n - 1];
		// 统计i位置右边小于i的个数
		for (int i = 0; i < n; ++i) {
			long count = 0;
			if (nums[i] == 1)
				continue;
			for (int j = i + 1; j < n; ++j) {
				if (nums[j] < nums[i])
					count++;
			}
			midNums[(int) (n - nums[i])] = count;
		}
//		System.out.print("Mid Nums: ");
//		printArr(midNums);
		return midNums;
	}

	// 新中介数 -> 新排列数
	private static void mid2permutation(long[] nums, int n, long[] midNums) {
		for (int i = 0; i < n; ++i)
			nums[i] = 0L;

		for (int i = 0; i < n - 1; ++i) {
			int count = 0;
			for (int j = n - 1; j >= 0; --j) {
				if (count == midNums[i] && nums[j] == 0) {
					nums[j] = (long) (n - i);
					break;
				} else if (nums[j] == 0) {
					count++;
				}
			}
		}

		int idx = -1;
		while (nums[++idx] != 0)
			;

		nums[idx] = 1L;
	}

	// 逆置
	private static void reverse(long[] midNums, int start, int end) {
		long temp;
		for (int i = start, j = end; i < j; ++i, --j) {
			temp = midNums[i];
			midNums[i] = midNums[j];
			midNums[j] = temp;
		}
	}

	/**
	 * 字典序计算
	 * 
	 * @param nums:给定的序列
	 * @param n:序列的个数
	 * @param k:要此序列后第k个序列,k分为k>0和k<0
	 * 9 1 3 8 3 9 6 4 7 5 2 1
	 */
	private static void dictOrder(long[] nums, int n, long k) {
		// 原排列 -> 原中介数
		long[] midNums = new long[n - 1];
		for (int i = 0; i < n - 1; i++) {
			long count = 0;
			for (int j = i + 1; j < n; ++j) {
				if (nums[i] > nums[j])
					count++;
			}
			midNums[i] = count;
		}
		// 原中介数 -> 新中介数
		midNums[n - 1 - 1] += k;
		long temp = 0;
		long carry = 0;
		if (k > 0) {
			for (int i = n - 1 - 1; i >= 0; --i) {
				temp = midNums[i] + carry;
				midNums[i] = temp % (n - i);
				carry = temp / (n - i);
				if (carry == 0)
					break;
			}
		} else {
			// 下面的carry代表的是借位, 是正数
			// 最后一位<0
			if (midNums[n - 1 - 1] < 0) {
				for (int i = n - 1 - 1; i >= 0; --i) {
					temp = midNums[i] - carry;
					// 都归正数了,可以结束了
					if (temp >= 0) {
						midNums[i] = temp;
						break;
					}
					if (temp % (n - i) == 0) {
						midNums[i] = 0L;
						carry = -1 * temp / (n - i);
					} else {
						midNums[i] = n - i + temp % (n - i);
						carry = 1 - temp / (n - i);
					}
				}
			}
		}

		// 新中介数 -> 新排列
		boolean[] temps = new boolean[n];
		for (int i = 0; i < n; ++i)
			temps[i] = true;
		int subsum = 0;  // 现在nums0~(n-1)所存数,为了计算最后一个
		for (int i = 0; i < n - 1; ++i) {
			midNums[i] += 1;
			int count = 0;
			for (int j = 0; j < n; ++j) {
				if (temps[j])
					count++;
				if (count == midNums[i]) {
					temps[j] = false;
					nums[i] = j + 1;
					subsum += nums[i];
					break;
				}
			}
		}
		nums[n - 1] = n * (n + 1) / 2 - subsum;

	}

	/**
	 * 递增进位制计算
	 * 
	 * @param nums
	 * @param n
	 * @param k
	 */
	/*
	 * Input 9 2 1 8 3 9 6 4 7 5 2 1 Output 8 4 9 6 1 7 5 2 3
	 */
	private static void incrementalCarry(long[] nums, int n, long k) {
		// 原排列 -> 原中介数
		long[] midNums = permutation2Mid(nums, n);

		// 原中介数 -> 新中介数:加减k
		midNums[n - 1 - 1] += k; // 最后一位做加法
		long temp = 0L, carry = 0L;
		if (k > 0) {
			for (int i = n - 1 - 1; i >= 0; --i) {
				temp = midNums[i] + carry;
				carry = temp / (n - i);
				midNums[i] = temp % (n - i);
			}
		} else {
			// 下面的carry代表的是借位, 是正数
			// 最后一位<0
			if (midNums[n - 1 - 1] < 0) {
				for (int i = n - 1 - 1; i >= 0; --i) {
					temp = midNums[i] - carry;
					// 都归正数了,可以结束了
					if (temp >= 0) {
						midNums[i] = temp;
						break;
					}
					if (temp % (n - i) == 0) {
						midNums[i] = 0L;
						carry = -1 * temp / (n - i);
					} else {
						midNums[i] = n - i + temp % (n - i);
						carry = 1 - temp / (n - i);
					}
				}
			}
		}

		// 新中介数 -> 新序列数
		mid2permutation(nums, n, midNums);
	}

	/**
	 * 递减进位制计算
	 * 
	 * @param nums
	 * @param n
	 * @param k
	 */
	/*
	 * Input 9 3 1 8 3 9 6 4 7 5 2 1 Output 8 9 3 6 4 7 5 2 1
	 */
	private static void degressiveCarry(long[] nums, int n, long k) {
		// 原排列 -> 原中介数
		long[] midNums = permutation2Mid(nums, n);
		// 逆置得递减进位的中介数
		reverse(midNums, 0, midNums.length - 1);

		// 原中介数 -> 新中介数:加减k
		midNums[n - 1 - 1] += k; // 最后一位做加法
		long temp = 0L, carry = 0L;
		if (k > 0) {
			for (int i = n - 1 - 1; i >= 0; --i) {
				temp = midNums[i] + carry;
				carry = temp / (i + 2);
				midNums[i] = temp % (i + 2);
			}
		} else {
//			TODO
			// 下面的carry代表的是借位, 是正数
			// 最后一位<0
			if (midNums[n - 1 - 1] < 0) {
				for (int i = n - 1 - 1; i >= 0; --i) {
					temp = midNums[i] - carry;
					// 都归正数了,可以结束了
					if (temp >= 0) {
						midNums[i] = temp;
						break;
					}
					if (temp % (i + 2) == 0) {
						midNums[i] = 0L;
						carry = -1 * temp / (i + 2);
					} else {
						midNums[i] = i + 2 + temp % (i + 2);
						carry = 1 - temp / (i + 2);
					}
				}
			}
		}
		reverse(midNums, 0, midNums.length - 1);
//		System.out.print("Mid Nums ± K: ");
//		printArr(midNums);

		// 新中介数 -> 新排列
		mid2permutation(nums, n, midNums);
	}

	/**
	 * 邻位对换算法
	 * 
	 * @param nums
	 * @param n
	 * @param k
	 */
	/*
	 * Input 9 4 1 8 3 9 6 4 7 5 2 1 Output 8 3 6 9 4 7 5 2 1
	 */
	private static void orthoSubstitution(long[] nums, int n, long k) {
		// 原排列 -> 中介数
		long[] midNums = new long[n - 1];

		for (int i = 0; i < n - 1; ++i) {
			int j = 0;
			int count = 0;

			while (nums[j] != i + 2)
				// 先统计左边比i+2小的
				if (nums[j++] < i + 2)
					count++;

			// 如果 i+2 为 奇数 ,其方向性决定于 b(i-1) 的奇偶性, 奇向右、偶向左 。
			if ((i + 2) % 2 == 1) {
				// 只有偶数向左的时候,才需要重新计数,奇数已经计数过了
				if (midNums[i - 1] % 2 == 0) {
					count = 0;
					while (j < n)
						if (nums[j++] < i + 2)
							count++;
				}
			}
			// 2 一定向左,如果 i 为 偶数 ,其方向性决定于 b(i-1) + b(i-2) 的奇偶性,同样是 奇向右、偶向左 。
			else {
				// 只有偶数向左的时候,才需要重新计数,奇数已经计数过了
				if (i + 2 == 2 || (midNums[i - 1] + midNums[i - 2]) % 2 == 0) {
					count = 0;
					while (j < n)
						if (nums[j++] < i + 2)
							count++;
				}
			}
			midNums[i] = (long) count;
		}

//		System.out.print("Mid Nums: ");
//		printArr(midNums);

		// 原中介数 -> 新中介数
		// 原中介数 -> 新中介数:加减k
		midNums[n - 1 - 1] += k; // 最后一位做加法
		if (k > 0) {
			long temp = 0L, carry = 0L;
			for (int i = n - 1 - 1; i >= 0; --i) {
				temp = midNums[i] + carry;
				carry = temp / (i + 2);
				midNums[i] = temp % (i + 2);
			}
		} else {
//					TODO
			// 下面的carry代表的是借位, 是正数
			// 最后一位<0
			if (midNums[n - 1 - 1] < 0) {
				long temp = 0L, carry = 0L;
				for (int i = n - 1 - 1; i >= 0; --i) {
					temp = midNums[i] - carry;
					// 都归正数了,可以结束了
					if (temp >= 0) {
						midNums[i] = temp;
						break;
					}
					if (temp % (i + 2) == 0) {
						midNums[i] = 0L;
						carry = -1 * temp / (i + 2);
					} else {
						midNums[i] = i + 2 + temp % (i + 2);
						carry = 1 - temp / (i + 2);
					}
				}
			}
		}

		for (int i = 0; i < n; ++i)
			nums[i] = 0L;

		for (int i = n - 2; i >= 0; --i) {
			int j;
			int count = 0;
			// i+2为奇,
			if (i % 2 == 1) {
				// 只看b(i-1)
				if (midNums[i - 1] % 2 == 1)
					// 向右第b(i)+1个空
					for (j = 0; j < n; j++) {
						if (count == midNums[i] && nums[j] == 0)
							break;
						else if (nums[j] == 0)
							count++;
					}
				else
					// 向左
					for (j = n - 1; j >= 0; j--) {
						if (count == midNums[i] && nums[j] == 0)
							break;
						else if (nums[j] == 0)
							count++;

					}
			}
			// i 为 2,一定向左 // i+2 为偶数
			else {
				// 要看b(i-1) + b(i-2)
				if (i + 2 != 2 && (midNums[i - 1] + midNums[i - 2]) % 2 == 1)
					// 向右
					for (j = 0; j < n; j++) {
						if (count == midNums[i] && nums[j] == 0)
							break;
						else if (nums[j] == 0)
							count++;

					}
				else
					// 向左
					for (j = n - 1; j >= 0; j--) {
						if (count == midNums[i] && nums[j] == 0)
							break;
						else if (nums[j] == 0)
							count++;

					}
			}
			nums[j] = (long) (i + 2);
		}

		int idx = -1;
		while (nums[++idx] != 0)
			;

		nums[idx] = 1L;
	}

}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值