详细讲解康托展开集齐基础例题+模板

单纯讲讲怎么用怎么算,说些人话。

预备概念:
排列:对于一个数字n的排列就是含有[1,n]所有数字的序列。也就是n取3,那么123,132,213就叫做n的排列,但是112,12,13等等这种就不是排列。

什么是康托展开呢?就是把一个n的全排列表示出来并且按照字典序排出来。n取三的全排列有6种,那么就对着6种按字典序小的顺序去排比如123的序号就是1,132的序号就是2.

首先我们来讲怎么根据给出的一个排列去求出他所代表的序号是多少,比如序列
2 1 4 3的序号。他的求法是这样的,看第一个数字是2,我们去需要知道目前还没用过的数字中2所在的位置-1的这个数字作为temp1好了, 目前没使用过的数字是1234(从小到大),2是第二大的数字,在减去一,减一后就是temp1 = 1;然后我们需要另一个数字temp2,这个数字是什么呢就是n减去当前在序列中这个数字的位置,就是temp2 = 4 - 1 = 3,在对temp2求阶乘也就是temp2 = 3! = 6;把temp1 , temp2乘起来,在用这个乘完的数字加上后面位数的temp1temp2
(过程算出来的数字就是这样的)
2:temp1 = 1, temp2 = 3! = 6,temp1 * temp2 = 6;
1:temp1 = 0(现在没使用过的数字是1,3,4),
temp2 = 2! = 2,temp1 * temp2 = 0;
4 :temp1 = 1,(1,2都使用过,现在剩下3,4),
temp2 = 1! = 1,temp1
temp2 = 1;
3: temp = 0(只剩下3),temp2 = 0! = 1;temp1*temp2 = 0;

那么对于2143这个序列的序号就是6+0+1+0 = 7,但是在最后的最后还要在加上1,让他变成7+1 = 8这才是正确答案,因为他的序号原本是从0开始算的,你算1234算出来就是0,我们让他从1开始算;

关于代码实现方面方面用树状数组去维护前缀和就可以知道某个数字到底现在排在第几位(没用过的时候就是1,用完就把他减成0),其实就是一个单点修改区间查询的问题。

那么现在我们再来问一个问题, 知道一个序号和n,我们怎么求他所代表的序列是哪一种序列?我们就假设n是4,序号是8,用刚才的例子.用t表示8
是这么求的先把序号-1,(我们上面是在最后加1的记得吗)。 t = 8 - 1 = 7;
有n个数,我们从第一个数开始算起,用i表示现在在算第几个数,
用t去除以(n - i)的阶乘得到一个数字,这个数字是什么?这个数字表示的是在未使用的数字里面排在第几个,但是我们还需要再加上一(记得我们上面算序号是-1的,这里就是反过来求也就是+1),而现在这个排名所代表的数字就是当前位置的数字,多看两遍,再看不懂我推给你看;
i = 1
int node = t / (4-i)! = 7 / 3 ! = 1;node + 1 = 2;
算出node = 2就是要未使用的数字里面排第2的就是(1234里面排第二的就是2),那么第1个数字就是2,(现在未使用的数字就是134),算完后t取t除(4-i)!的模数,即t变成 t = t % (4 - i)! =1;
i = 2
int node = t / (4 - i)! = 1 / 2 ! = 0, node + 1 = 1;
要求未使用的数字里面排第一的(现在未使用的数字是134,排第一个的就是1), 在更新t,t = t % (4 - i)! = 1, 现在未使用的数字是(3,4);
i = 3
int node = t / (4 - i)! = 1 / 1! = 1,node + 1 = 2;
要求未使用的数字里面排第二的(现在未使用的数字是34,排第二个的就是4)
,更新t ,t = t % (4 - i)! = 0;(现在未使用的数字是3)
i = 4
int node = t / (4 - i)! = 0 / 0! = 1,node + 1 = 1;
取未使用的数字里面排第一位,就是3.算法结束
这个就是求的过程,希望各位看懂了康托展开和逆展开是怎么算出来的。在实现逆展开方面我是选择树状数组+二分去找出现在未使用的数字的第k大,当然线段树也可以,复杂度甚至更低(树状数组是两个log,线段树一个log)。
洛谷p3014这个就是纯裸康托展开+逆展开例题。
我没怎么讲具体树状数组怎么实现因为这篇主要是来用说一说康托展开的。如果对于我代码里面树状数组部分有何不懂可以留言。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include 
#define debug cout<<"*********degug**********";
#define int long long
#define RE register
#define yn yn_
using namespace std;
const long long max_ = 1530000 + 7;
const int mod = 998244353;
const int inf = 1e9;
const long long INF = 1e18;
int read() {
	int s = 0, f = 1;
	char ch = getchar();
	while (ch<'0' || ch>'9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0'&&ch <= '9') {
		s = s * 10 + ch - '0';
		ch = getchar();
	}
	return s * f;
}
inline int min(int a, int b) {
	return a < b ? a : b;
}
inline int max(int a, int b) {
	return a > b ? a : b;
}
int n,value[max_];

int c[100];
int low_bit(int x) {
	return (x&(-x));
}

void change(int x, int value) {
	while (x <= n) {
		c[x] += value;
		c[x] %= mod;
		x = x + low_bit(x);
	}
}

int sum_(int x) {
	//算1到x的总和
	int ans = 0;
	while (x != 0) {
		ans += c[x];
		ans %= mod;
		x = x - low_bit(x);
	}
	return ans%mod;
}
int erfen(int L, int R, int aim) {
	if (L == R)return L;
	int mid = L + (R - L) / 2;
	if (sum_(mid) >= aim) {
		return erfen(L, mid, aim);
	}
	else
	return erfen(mid + 1, R, aim);
}
signed main() {
	int T;
	n = read(), T = read();
	value[0] = 1;
	for (int i = 1; i <= 20; i++) {
		value[i] = value[i - 1]  * i;
	}
	while (T--){
		char ch;
		cin >> ch;
		if (ch == 'P') {
			//康托逆展开
			memset(c, 0, sizeof(c));
			for (int i = 1; i <= n; i++)
				change(i, 1);
			int temp = read(); temp--;
			for (int i = 1,num = n - 1; i <= n;num--, i++) {
				int aim = temp / value[num] + 1; temp %= value[num];
				int t = erfen(1, n, aim);
				cout << t << " ";
				change(t, -1);
			}
			cout << "\n";
		}
		else {//康托展开
			memset(c, 0, sizeof(c));
			for(int i =1; i <= n ; i++)
				change(i, 1);
			int ans = 1;//要从1开始
			for (int i = 1, jiecheng = n - 1; i <= n; jiecheng--, i++) {
				int temp = read();
				ans += value[jiecheng] * sum_(temp - 1);
				change(temp, -1);
			}
			cout << ans << "\n";
		}
	}
	return 0;
}

你可能感兴趣的:(学习,算法)