康托展开&逆康托展开
这玩意的题目都比较少.....因为实在是太裸了
康托展开是什么?
大概就是给你一个全排列,让你求出它在所有全排列里面的排名....
说起来比较难说,给例子你就懂了:
例如:
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
结束!
例子一:
例子二:
再给一个例子:
如果你懂了怎么求,你会认为这个算法的时间复杂度是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;
}