蓝桥杯模拟赛--康托展开式--全排列(不重复数据)

题目是这样的:
标题: 排列序数

X星系的某次考古活动发现了史前智能痕迹。
这是一些用来计数的符号,经过分析它的计数规律如下:
(为了表示方便,我们把这些奇怪的符号用a~q代替)
abcdefghijklmnopq 表示0
abcdefghijklmnoqp 表示1
abcdefghijklmnpoq 表示2
abcdefghijklmnpqo 表示3
abcdefghijklmnqop 表示4
abcdefghijklmnqpo 表示5
abcdefghijklmonpq 表示6
abcdefghijklmonqp 表示7
.....

问题一:
在一处石头上刻的符号是:
bckfqlajhemgiodnp
请你计算出它表示的数字是多少?

问题二:
求第22952601027516个排列的符号排列是?


先学习康托展开式:转自http://www.cnblogs.com/1-2-3/archive/2011/04/25/generate-permutation-part2.html

康托展开的公式是 X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 其中,ai为当前未出现的元素中是排在第几个(从0开始)。
  这个公式可能看着让人头大,最好举个例子来说明一下。例如,有一个数组 s = ["A", "B", "C", "D"],它的一个排列 s1 = ["D", "B", "A", "C"],现在要把 s1 映射成 X。n 指的是数组的长度,也就是4,所以
X(s1) = a4*3! + a3*2! + a2*1! + a1*0!
关键问题是 a4、a3、a2 和 a1 等于啥?
a4 = "D" 这个元素在子数组 ["D", "B", "A", "C"] 中是第几大的元素。"A"是第0大的元素,"B"是第1大的元素,"C" 是第2大的元素,"D"是第3大的元素,所以 a4 = 3。
a3 = "B" 这个元素在子数组 ["B", "A", "C"] 中是第几大的元素。"A"是第0大的元素,"B"是第1大的元素,"C" 是第2大的元素,所以 a3 = 1。
a2 = "A" 这个元素在子数组 ["A", "C"] 中是第几大的元素。"A"是第0大的元素,"C"是第1大的元素,所以 a2 = 0。
a1 = "C" 这个元素在子数组 ["C"] 中是第几大的元素。"C" 是第0大的元素,所以 a1 = 0。(因为子数组只有1个元素,所以a1总是为
所以,X(s1) = 3*3! + 1*2! + 0*1! + 0*0! = 20



通过康托逆展开生成全排列

  如果已知 s = ["A", "B", "C", "D"],X(s1) = 20,能否推出 s1 = ["D", "B", "A", "C"] 呢?
  因为已知 X(s1) = a4*3! + a3*2! + a2*1! + a1*0! = 20,所以问题变成由 20 能否唯一地映射出一组 a4、a3、a2、a1?如果不考虑 ai 的取值范围,有
3*3! + 1*2! + 0*1! + 0*0! = 20
2*3! + 4*2! + 0*1! + 0*0! = 20
1*3! + 7*2! + 0*1! + 0*0! = 20
0*3! + 10*2! + 0*1! + 0*0! = 20
0*3! + 0*2! + 20*1! + 0*0! = 20
等等。但是满足 0 <= ai <= n-1 的只有第一组。可以使用辗转相除的方法得到 ai,如下图所示:

知道了a4、a3、a2、a1的值,就可以知道s1[0] 是子数组["A", "B", "C", "D"]中第3大的元素 "D",s1[1] 是子数组 ["A", "B", "C"] 中第1大的元素"B",s1[2] 是子数组 ["A", "C"] 中第0大的元素"A",s[3] 是子数组 ["C"] 中第0大的元素"C",所以s1 = ["D", "B", "A", "C"]。

import java.util.ArrayList;
import java.util.Iterator;

public class Main6 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		/* abcdefghijklmnopq 表示0
		   abcdefghijklmnoqp 表示1
		   abcdefghijklmnpoq 表示2
		   abcdefghijklmnpqo 表示3
		 * 求bckfqlajhemgiodnp代表的数字
		 * */
		long a[] = new long[17];
		GetFactorial(a);
		long sum = Getansnum("bckfqlajhemgiodnp",a);
		System.out.println(sum);
		
		/* abcdefghijklmnopq 表示0
		   abcdefghijklmnoqp 表示1
		   abcdefghijklmnpoq 表示2
		   abcdefghijklmnpqo 表示3
		   求第22952601027516个排列的符号排列是?
		 * */      
		long num = 22952601027516L;
		String ans = Getanssign(a,num);
		System.out.println(ans);
	}

	
	private static String Getanssign(long[] a, long num) {
		ArrayList<Character> sub = new ArrayList<>();
		ArrayList<Character> result = new ArrayList<>();
		for (int i = 0; i <=16; i++) {
			sub.add((char) ('a'+i));//生产a-q升序排列;
		}
		for(int i = 16;i>=0;i--){
			int ai;
			if (i==0) {
				ai = 0;
			}else {
				ai = (int) (num/a[i]);
			}
			char temp = sub.get(ai);
			result.add(temp);
			sub.remove(sub.indexOf(temp));
			if (i==0) {
				num = 0;
			}else {
				num = num%a[i];	
			}
		}
		Iterator<Character> ansstring = result.iterator();
		StringBuffer answer = new StringBuffer();
		while(ansstring.hasNext()){
			answer.append(ansstring.next());
		}
		return answer.toString();
	}


	/**
	 * @param s 已知的符号序列
	 * @param a 阶乘数组
	 * @return s符号序列的是第几个排列
	 */
	private static long Getansnum(String s, long[] a) {
		// TODO Auto-generated method stub
		long sum = 0;
    	char temp[] = s.toCharArray();
    	int num[] = new int[s.length()];
    	for (int i = 0; i < temp.length; i++) {
			num[16-i] = temp[i];
		}
		for (int i = 16; i >=0; i--) {
			sum = sum + a[i]*getan(num,i);
		}
		return sum;
	}

	/**
	 * @param a 数组a储存阶乘
	 */
	private static void GetFactorial(long[] a) {
		// TODO Auto-generated method stub
		for (int i = 0; i < a.length; i++) {
			if (i==0){
				a[0] = 0;
			}else if (i==1) {
				a[1] = 1;
			}else {
				a[i] = a[i-1]*i;
			}
		}
	}

	/**
	 * @param num 输入当前的数组
	 * @param i 
	 * @return 返回i是第几大的数
	 */
	private static long getan(int[] num, int i) {
		// TODO Auto-generated method stub
		int n = 0 ;
		for(int j = i; j>=0; j--){
			if(num[i]>num[j]) n++;
		}
		return n;
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

obession

觉得有用可以打赏咖啡一杯~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值