康托展开&逆康托展开

这玩意的题目都比较少.....因为实在是太裸了

康托展开是什么?

大概就是给你一个全排列,让你求出它在所有全排列里面的排名....

说起来比较难说,给例子你就懂了:

例如:

5
4 5 1 3 2

它的排名怎么求呢?
解法一:全排列后看排名就行了。很简单的求出来是92。
那么假如是这样子呢?

15
1 3 2 5 4 7 8 9 11 10 15 13 14 12 6

它的排名是:6267347514
莫非你还能用全排列......

求法是怎么样的呢?

康托展开

对于例子1:

4 5 1 3 2

排名一开始是1

发现比4 小的数有 1 2 3,那么排名加上 (4!) * 3 = 73
再发现比5小的数有1 2 3 4 ,但是4已经在第一位了,所以排名加上(3!) * 3 = 91
然后是1, 没有比1小的数。to be continue......
再次是3,比3小的是1,2,1已经在第三位了,排名加上(1!) * 1 = 92
最后是2,已经没有满足条件的比2小的数了(1已经在第3位了).... to be continue

结束!

例子一:

B4AZl9.png

例子二:

B4AVSJ.png

再给一个例子:

B4A1YD.png

如果你懂了怎么求,你会认为这个算法的时间复杂度是O(\(n^2\))的,但是,求一个数有多少个数比它小,难道不是可以用树状数组维护吗?每次查询完一个数后,就相当于这个数删除掉,后面的数计算贡献就不会计算这个数了。

大概就是这样子:

代码(luogu模板题,对\(998244353\)取模):

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int MAXN = 1000005,Mod = 998244353;

int n,Np[MAXN];//NP[i]表示(i!)( i的阶乘 )
int C[2 * MAXN];//树状数组 ,开两倍空间防爆

int lowbit(int x)
{
	return x & (-x);
}

int add(int x,int k)
{
	while(x <= n)C[x] += k ,x += lowbit(x);//树状数组板子修改
	return 0;
}

int Get(int x)
{
	int ans = 0;
	while(x)ans += C[x] , x -= lowbit(x);//树状数组板子求和
	return ans;
}

signed main()
{
	cin >> n;
	int head = n - 1,Ans = 1;
	Np[0] = 1;
	for(int i = 1 ; i <= n - 1; i ++)Np[i] = Np[i - 1] * i ;//预处理出阶乘
	for(int i = 1 ; i <= n ; i ++)add(i,1);//预处理树状数组
	for(int i = 1 ; i <= n ; i ++)
	{
		int x;
		cin >> x;
		Ans += Np[head] * Get(x - 1) % Mod;//求和即可,Get(x-1)求出来的就是当前比x小的数的个数
		Ans %= Mod;
		add(x,-1);//每次查询后把这个数给删除,就给后面加上-1即可
		head --;
	}
	cout << Ans % Mod;
	return 0;
}

逆康托展开

思考一个问题,给你一个全排列的排名以及全排列的长度,怎么还原出这个全排列?
简单!我直接求出所有全排列,然后还原即可!
现在给出:

全排列长度为5,排名为8

这个简单,直接暴力!
但是我不可能让你可以暴力过去的。

全排列长度为12,排名为303368667

继续你的全排列表演.....

这个东西怎么求?

上文提到了,我们是如何康拓展开的?

也就是看一个数有多少个数比它小,然后乘上\(n-i\)的阶乘。

那么我们如何还原?
以第一个例子为例:

假设\(Arr[]\)是我们要求的全排列

首先把给出的排名减1,因为一开始排名是从1开始的,你观察一下上面的求康托展开的过程.

现在\(number = 7(number是指排名)\),然后我们除以\((4!)\)并且向下取整,发现是0,那么第一位就是1,因为只有比1小的数是0个 \(Arr = {1}\)

然后我们的排名对\(4!\)取模,现在\(number = 7\),接着我们用\(number\)除以\((3!)\)向下取整,发现是1,也就是比第二位小的数是1个,但是由于1已经在 前面出现过,所以这个数只能是3 \(Arr = {1,3}\)

接着我们\(number\)\((3!)\)取模,得出\(number = 1\),我们用\(number\)除以\((2!)\)向下取整,发现等于0,目前,只有比2小的数的个数为0,(因为1已经出现过),所以第三位为2 \(Arr = {1,3,2}\)

我们用\(number\)\((2!)\)取模,得出\(number = 1\),我们用\(number\)除以\((1!)\)向下取整,发现等于0,目前只有比4小的数的个数为0,\((1,3,2已经在Arr中)\),所以第四位为4 \(Arr = {1,3,2,4}\)

最后一位显然是\(5\),所以原全排列为:1 3 2 4 5
就是这样子,over.

你发现,找一个数,比它小的数为个数\(k\),那么实际上你就只要找到目前第 \(k + 1\) 大的数即可,这个直接用平衡树维护即可。

然后就没了,真的没了。

对于第二个例子不解释了,当做大样例吧hiahiahia,
答案是:

8 7 9 1 2 4 6 5 3 11 10 12

请注意,下面这份代码因为没有取模,所以支持的全排列的范围仅为1~20!

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1000005,INF = 1000000000;
int cnt = 0,root,n,Q,NP[MAXN];

struct Tree {
	int lc,rc,data,val;
	int siz,cnt;
} T[MAXN * 2];

void updata(int x)
{
	T[x].siz = T[T[x].lc].siz + T[T[x].rc].siz + T[x].cnt;
	return ;
}

void zig(int &x)
{
	int p = T[x].lc;
	T[x].lc = T[p].rc , T[p].rc = x , x = p;
	updata(T[x].rc),updata(x);
	return ;
}

void zag(int &x)
{
	int p = T[x].rc;
	T[x].rc = T[p].lc,T[p].lc = x , x = p;
	updata(T[x].lc),updata(x);
	return ;
}

int New(int val)
{
	cnt ++;
	T[cnt].cnt = T[cnt].siz = 1;
	T[cnt].data = rand();
	T[cnt].val = val;
	return cnt;
}

void build()
{
	New(-INF),New(INF);
	root = 1;
	T[1].rc = 2;
	updata(root);
	return ;
}

void insert(int &x ,int pos)
{
	if(x == 0)
	{
		x = New(pos);
		return ;
	}
	if(T[x].val == pos)
	{
		T[x].cnt ++;
		updata(x);
		return ;
	}
	if(pos < T[x].val)
	{
		insert(T[x].lc,pos);
		if(T[T[x].lc].data > T[x].data)zig(x);
	}
	else 
	{
		insert(T[x].rc,pos);
		if(T[T[x].rc].data > T[x].data)zag(x);
	}
	updata(x);
	return ;
}

void del(int &x,int pos)
{
	if(T[x].val == pos)
	{
		if(T[x].cnt > 1)
		{
			T[x].cnt --;
			updata(x);
			return ;
		}
		if(T[x].lc || T[x].rc)
		{
			if(T[T[x].rc].data > T[T[x].lc].data || T[x].lc == 0)
				zag(x),del(T[x].lc,pos);
			else zig(x),del(T[x].rc,pos);
			updata(x);
		}
		else x = 0;
		return ;
	}
	if(pos < T[x].val)del(T[x].lc,pos);
	else del(T[x].rc,pos);
	updata(x);
	return ;
}

int GetKth(int x,int pos)
{
	if(T[T[x].lc].siz >= pos)return GetKth(T[x].lc,pos);
	if(T[T[x].lc].siz + T[x].cnt >= pos)return T[x].val;
	if(T[T[x].lc].siz + T[x].cnt< pos)return GetKth(T[x].rc,pos - T[x].cnt - T[T[x].lc].siz);
}

int Kanto()
{
	NP[0] = 1;
	for(int i = 1 ; i <= n ; i ++)NP[i] = NP[i - 1] * i;
	int head = n - 1;
	Q --;
	for(int i = 1 ; i <= n ; i ++)insert(root,i);
	for(int i = 1 ; i <= n ; i ++)
	{
		int k = Q / NP[head];
		Q %= NP[head];
		int Kthnumber = GetKth(root,k + 2);
		cout << Kthnumber << " ";
		del(root,Kthnumber);
		head --;
	}
}

signed main()
{
	srand(time(NULL));
	build();
	cin >> n >> Q;
	Kanto();
	return 0;
}
posted @ 2020-11-07 09:08  MYCui  阅读(120)  评论(0编辑  收藏  举报