【组合数学与计数】Uim的情人节礼物·其之壱

本文介绍了如何在情人节为后宫准备礼物时,利用编程找到字典序上一个的排列。通过三种不同的方法:模拟、`prev_permutation`函数和康托展开与逆康托展开,解决这一问题。示例中给出了详细的操作过程,并提供了代码实现。
摘要由CSDN通过智能技术生成

Uim的情人节礼物·其之壱

题目描述

情人节到了,Uim打算给他的后宫们准备情人节礼物。UIm一共有N(1<=N<=9)个后宫妹子(现充去死 挫骨扬灰!)。

为了维护他的后宫的稳定。他通过编程,得出了一个送礼物的最佳顺序。这个我们管不着。

然而他认为,如果什么事情做得太圆满不是什么好事。于是他希望得到 原定顺序 的 前一个字典序的序列。

输入格式

第一行一个整数N

第二行N个整数,表示原定排列

输出格式

前一个排列

样例 #1

样例输入 #1

3
1 3 2

样例输出 #1

1 2 3

提示

若当前排列已经是第一个,则输出’ERROR’(引号不输出)

Solution

1.模拟
2.prev_permutation函数
3.康泰展开与逆康托展开

Code

1.模拟

bool cmp(int a,int b){
	return a > b;
}

	int k = n;
	while(k != 0 && a[k] >= a[k - 1]) k --;     //找到第一个左边的数比自己小的数
	
	if(!k){       //如果找不到,说明已经是最小字典序
		puts("ERROR");
		return 0;
	}
	
	k -= 1;			//标记位置
	int p = lower_bound(a + j + 1,a + n + 1,a[j] - 1) - a;     //二分搜素j后面找到比a[j]小1的数
	swap(a[j], a[p]);			//交换他们的位置
	
	sort(a + j + 1, a + n + 1, cmp);    //将a[j]后面的数从大到小排序
	for(int i = 1; i <= n ; i ++ )cout << a[i] << " ";

举个例子:

652134 的前一个字典序为651432,从第1位找到第6位,可以发现找到第3位两式出现不同,前一个字典序第3位后面的数按照从大到小排序。

一般地:
x 1 x 2 … x k x k+1 x k+2 x k+3…x n 前字典序为x 1 x 2 … y k y k+1 y k+2 y k+3…y n

y k = x k - 1

y k+1 y k+2 y k+3…y n为x k x k+1 x k+2 x k+3…x n除去y k 后的逆序

2.prev_permutation函数

prev_permutation函数可以制造前一个排列,如果已经为第一个,则返回false

	if(prev_permutation(a,a+n))  //如果为真就输出数组
        for(int i=0;i<n;i++)
            cout<<a[i]<<" ";
    else cout<<"ERROR";   //否则输出ERROR

3.康托展开与逆康托展开

康托展开是一种类似于hash的做法,是一个全排列到一个自然数的双射,常用于构建hash表时的空间压缩
简单来说,就是用把数列映射为一个数,一个数来表示一个排列,而这个数实际上就是当前排列在所有排列中的排名
逆康托展开就是要把自然数映射回排列

#include<bits/stdc++.h>
using namespace std;
typedef vector<int> VI;
int n,a[15],f[15];
VI v;

int cantor(){
	int ans = 0;
	for(int i = 1; i <= n; i ++ ){
		int cnt = 0;
		for(int j = i + 1; j <= n; j ++ ){
			if(a[i] > a[j])cnt ++;        //记录下i的右边有几个比i小的数
		}
		ans += cnt * f[n - i]; //固定1-(i-1),i-n共有cnt * f[n - i]种情况
	}
	return ans;
}

void incantor(int k){
	
	for(int i = 1; i <= n; i ++ )v.push_back(i);   //预处理
	for(int i = 1; i <= n - 1; i ++ ){
		a[i] = v[k / f[n - i]];	    //根据i的右边有几个比i小的数算出第i个数是什么
		v.erase(v.begin() + k / f[n - i]);   //删除已固定的数
		k %= f[n - i];	
	}
	a[n] = v[0];
}

int main(){
	
	cin >> n;
	f[1] = 1;
	for(int i = 2; i <= 9; i ++)f[i] = f[i - 1] * i; //预处理出阶乘
	for(int i = 1; i <= n ; i ++ )cin >> a[i];
	incantor(cantor() - 1);
	for(int i = 1; i <= n ; i ++ )cout << a[i] << " ";
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值