zoukankan      html  css  js  c++  java
  • 康托展开&逆康托展开

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

    康托展开是什么?

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

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

    例如:

    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;
    }
    
  • 相关阅读:
    docker容器中查看容器linux版本
    mac 安装docker
    音视频流处理
    激光雷达
    sick 激光
    Ubuntu 14.04 安装Docker
    W: GPG error: http://ppa.launchpad.net trusty InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 8CF63AD3F06FC659
    rtp发送 h265
    pytorch 环境搭建
    Linux 修改文件夹的用户组权限
  • 原文地址:https://www.cnblogs.com/MYCui/p/13939790.html
Copyright © 2011-2022 走看看