【康托展开 & 逆康托展开】求一个排列在全排列中的排名 洛谷P5367 & 给定一个排名求排列

题目在这里

求一个排列在全排列中的排名。

好像是一个比较经典的问题,类似的问题还有,求子串的排名等等 我在胡说什么

康托展开说白了就是一个hash方法。
方法如下:
ans = 1+sigma(a[i]*(n-i))

例如:对于一个排列 1 2 3 4 5
其进制变换后就是 (0 0 0 0 0)
对于另一个排列 1 4 5 2 3
其进制变换后就是(0 2 2 0 0)
为啥呢?
构造方法如下:
首位是1,在(1,2,3,4,5)中是第一个,故为0
第二位4,在(4,5,2,3)中是第三个,故为2
第三位是5,在(5,2,3)中是第三个,故位2
第四位是2,在(2,3)中是第一个,故为0
第五个是3,自然为0

于是第i位的值就是a[i]减去它左边比他小的个数再减1

将其转换成10进制就是
for(int i=1;i<n;i++) ans = (ans+a[i])*(n-i)

显然,康托展开可以用树状数组搞

于是,代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+7;
const int mod = 998244353;
int n;
int sum[maxn],a[maxn];
void add(int p,int x)
{
	for(int i=p;i<maxn;i+=(i & -i)) sum[i] += x;
}
int query(int p)
{
	int ans = 0;
	for(int i=p;i;i-=(i & -i)) ans += sum[i];
	return ans;
}
int main()
{
	scanf("%d",&n);
	int ans = 0,fac = 1;
	for(int i=n;i>=1;i--) scanf("%d",a+i);
	for(int i=1;i<=n;i++)
	{
		int tmp = query(a[i]);
		ans = (ans+1LL*tmp*fac)%mod;
		fac = 1LL*fac*i%mod;
		add(a[i],1);
	}
	printf("%d\n",ans+1);
	return 0;
}

逆康托展开就是将数字映射成排列。

将上述变换逆变换即是逆康托展开。

例:
对于1,2,3,4,5.求排名为10的排列。
(1) 10-1 = 9
(2) 第一个数:9/(5-1)! = 0…9,所以第一个数就是第0个未出现的数,即:1
(3)第二个数: 9/(4-1)! = 1…3,所以第二个数就是第1个未出现的数,即:3
(4)第三个数: 3/(3-1)! = 1…1,所以第三个数就是第1个未出现的数,即:4
(5)第四个数: 1/(2-1)! = 1…0,所以第四个数就是第1个未出现的数,即:5
(6)第五个数: 0/(1-1)! = 0…0,所以第五个数就是第0个未出现的数,即:2

故第10个排列就是1 3 4 5 2。

代码先不放了,这大家肯定都会写啊…

发布了159 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/KIKO_caoyue/article/details/99819198
今日推荐