彻底搞懂康托和逆康托
康拓展开
康托展开表示的是当前排列在n个不同元素的全排列中的名次
ans = an*(n-1)! + an-1 * (n-2)! + …+ a2 * 1! + a1 * 0!
其中表示第i个元素在未出现的元素中排列第几。
举个简单的例子:
对于排列4213来说,
(1)4在4213中排第3,注意从0开始,
(2)2在213中排第1,
(3)1在13中排第0,
(4)3在3中排第0,即:
这样得到4213在所有排列中排第ans=20(除第一个以外,比如1234这个排列,第20个是4132,第21个才是4213)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 18;
ll f[maxn];
void init()
{
ll sum = 1;
for (ll i = 1; i <= 18; i++) {
sum *= i;
f[i] = sum;
}
}
ll Work(string str)
{
int len = str.length();
ll ans = 0;
for (int i = 0; i < len; i++)
{
int tmp = 0;
for (int j = i+1; j < len; j++)
{
if (str[j] < str[i]) tmp++;
}
ans += tmp*f[len-i-1];
}
return ans;
}
int main()
{
init();
string s = "bckfqlajhemgiodnp";
cout << Work(s) << endl;
return 0;
}
//22952601027516
Java代码实现:
package 康拓;
//求当前排列是第几个排列
import java.util.*;
public class 康拓 {
static Long[] f = new Long[100];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
init();
while (in.hasNext()) {
String a = in.next();
int n = a.length();
solve(a, n);
}
}
public static void init() {
f[0] = 1L;
f[1] = 1L;
for (int i = 2; i < 20; i++) {
f[i] = (Long)f[i-1]*i;
}
}
private static void solve(String str, int n) {
int ans = 0;
for (int i = 0; i < str.length() - 1; i++) {
char a = str.charAt(i);
int cnt = 0;
for (int j = i+1; j < str.length(); j++) {
char b = str.charAt(j);
if (b < a) {
cnt++;
}
}
ans = (int) (ans + (cnt*f[str.length() - i - 1]));
}
System.out.println(ans);
}
}
还有就是逆康托:
例 {1,2,3,4,5}的全排列,并且已经从小到大排序完毕
找出第96个数
(1)首先用96-1得到95
(2)用95去除4! 得到3余23
(3)用23去除3! 得到3余5
(4)用5去除2!得到2余1
(5)用1去除1!得到1余0
计算:
(1)有3个数比它小的数是4,所以第一位是4
(2)有3个数比它小的数是4但4已经在之前出现过了,所以是5(因为4在之前出现过了所以实际比5小的数是3个)
(3)有2个数比它小的数,是3
(4)有1个数比它小的数,是2
(5)最后一个数只能是1
所以这个数是45321
那如果一个原始数是字符串,或者大整数呢,
下面考虑了long long 范围内也就是字符串18内及以内的数的排列查找第几个数。
比如传入m = 1234, n = 16,那这个结果就是3241,
比如传入字符串m =“abcdefghijklmnopq”, n = 22952601027517,
那这个结果就是”bckfqlajhemgiodnp”,也就是它的第22952601027517排列数是”bckfqlajhemgiodnp”。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 12345
ll f[6];
void init(string m)
{
ll sum = 1;
for (ll i = 1; i <= m.length(); i++)
{
sum *= i;
f[i] = sum;
}
}
void Work(string m, ll n)
{
n--;
vector<char> v;
vector<char> result;
for (ll i = 0; i < m.length(); i++)
v.push_back(m[i]);
//注意下标vector的下标是从0开始的
for (ll i = m.length()-1; i >= 1; i--)
{
ll r = n % f[i];
ll s = n / f[i];
n = r;
//sort(v.begin(), v.end());
result.push_back(v[s]);
v.erase(v.begin()+s);
}
//最后一个
result.push_back(v[0]);
for (ll i = 0; i < m.length(); i++)
{
cout << result[i];
}
cout << endl;
}
int main()
{
string m;
ll n;//第 n 个
cin >> m;
cin >> n;
init(m);
Work(m, n);
return 0;
}
Java代码实现:
package 康拓;
//给定一定数n,求指定排列的第n个数是啥
import java.util.ArrayList;
import java.util.Scanner;
public class 逆康拓 {
static Long[] f = new Long[100];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
init();
while (in.hasNext()) {
String a = "abc";
ArrayList<Character> list = new ArrayList<Character>();
for (int i = 0; i < a.length(); i++) {
list.add(a.charAt(i));
}
int n = in.nextInt();
solve(n, list, a.length());
}
}
public static void init() {
f[0] = 1L;
f[1] = 1L;
for (int i = 2; i < 20; i++) {
f[i] = (Long)f[i-1]*i;
}
}
private static void solve(int n, ArrayList<Character> list, int t) {
String result = "";
n -= 1;
for (int i = t-1; i > 0; i--) {
int k = (int)(n/(f[i]));
result = (result + (list.get(k)));
list.remove(k);
n = (int)(n%(f[i]));
}
if (list.size() != 0) result = result + list.get(0);
System.out.println(result);
}
}