T27018: 康托展开
http://cs101.openjudge.cn/practice/27018/ 求 1∼N 的一个给定全排列在所有 1∼N 全排列中的排名。结果对 998244353取模。
输入 第一行一个正整数 N。
第二行 N 个正整数,表示 1∼N 的一种全排列。 输出 一行一个非负整数,表示答案对 998244353 取模的值。 样例输入
Sample1 in:
3
2 1 3
Sample1 output:
3样例输出
Sample2 in:
4
1 2 4 3
Sample2 output:
2提示: 对于100%数据,
思路:容易想到的方法是把所有排列求出来后再进行排序,但事实上有更简单高效的算法来解决这个问题,那就是康托展开。
康托展开是一个全排列到一个自然数的双射,常用于构建特定哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的次序编号,因此是可逆的。即由全排列可得到其次序编号(康托展开),由次序编号可以得到对应的第几个全排列(逆康托展开)。
康托展开的表达式为:
其中:X 为比当前排列小的全排列个数(X+1即为当前排列的次序编号);n 表示全排列表达式的字符串长度;
表示原排列表达式中的第 i 位(由右往左数),前面(其右侧) i-1 位数有多少个数的值比它小。
例如求 5 2 3 4 1 在 {1, 2, 3, 4, 5} 生成的排列中的次序可以按如下步骤计算。 从右往左数,i 是5时候,其右侧比5小的数有1、2、3、4这4个数,所以有4×4!。 是2,比2小的数有1一个数,所以有 1×3!。 是3,比3小的数有1一个数,为1×2!。 是4,比4小的数有1一个数,为1×1!。 最后一位数右侧没有比它小的数,为 0×0!=0。 则 4×4!+1×3!+1×2!+1×1!=105。 这个 X 只是这个排列之前的排列数,而题目要求这个排列的位置,即 5 2 3 4 1排在第 106 位。
同理,4 3 5 2 1的排列数:3×4!+2×3!+2×2!+1×1!=89,即 4 3 5 2 1 排在第90位。 因为比4小的数有3个:3、2、1;比3小的数有2个:2、1;比5小的数有2个:2、1;比2小的数有1个:1。
参考代码如下。
MOD = 998244353 # Time Limit Exceeded, 内存7140KB, 时间18924ms
fac = [1]
def cantor_expand(a, n):
ans = 0
for i in range(1, n + 1):
count = 0
for j in range(i + 1, n + 1):
if a[j] < a[i]:
count += 1 # 计算有几个比他小的数
ans = (ans + (count * fac[n - i]) % MOD) % MOD
return ans + 1
a = [0]
N = int(input()) # 用大写N,因为spyder的debug,执行下一条指令的命令是 n/next。与变量n冲突。
for i in range(1, N + 1):
fac.append((fac[i - 1] * i) % MOD) # 整数除法具有分配律
*perm, = map(int, input().split())
a.extend(perm)
print(cantor_expand(a, N))用C++也是超时
#include<iostream> // Time Limit Exceeded, 内存960KB, 时间1986ms
using namespace std;
const long long MOD = 998244353;
long long fac[1000005]={1};
int cantor_expand (int a[],int n){
int i, j, count;
long long ans = 0 ;
for(i = 1; i <= n; i ++){
count = 0;
for(j = i + 1; j <= n; j ++){
if(a[j] < a[i]) count ++; // 计算有几个比它小的数
}
ans = (ans + (count * fac[n-i]) % MOD ) % MOD;
}
return ans + 1;
}
int a[1000005];
int main()
{
int N;
//cin >> N;
scanf("%d", &N);
for (int i=1; i<=N; i++){
fac[i] = (fac[i-1]*i)%MOD;
}
for (int i=1; i<=N; i++)
//cin >> a[i];
scanf("%d",&a[i]);
cout << cantor_expand(a,N) << endl;
return 0;
}优化
康托展开用
树状数组(Binary Indexed Tree)
实现树状数组的核心部分,包括了三个重要的操作:lowbit、修改和求和。
lowbit函数:
lowbit(x)是用来计算x的二进制表示中最低位的1所对应的值。它的运算规则是利用位运算(x & -x)来获取x的最低位1所对应的值。例如,lowbit(6)的结果是2,因为6的二进制表示为110,最低位的1所对应的值是2。-x是x的补码表示。对于正整数
x,-x的二进制表示是x的二进制表示取反后加 1。6的二进制表示为110,取反得到001,加 1 得到010。-6的二进制表示为11111111111111111111111111111010(假设 32 位整数)。6 & -6的结果:110与11111111111111111111111111111010按位与运算,结果为010,即2。update函数:这个函数用于修改树状数组中某个位置的值。参数
x表示要修改的位置,参数y表示要增加/减少的值。函数使用一个循环将x的所有对应位置上的值都加上y。具体的操作是首先将x位置上的值与y相加,然后通过lowbit函数找到x的下一个需要修改的位置,将该位置上的值也加上y,然后继续找下一个位置,直到修改完所有需要修改的位置为止。这样就完成了数组的修改。getsum函数:这个函数用于求解树状数组中某个范围的前缀和。参数
x表示要求解前缀和的位置。函数使用一个循环将x的所有对应位置上的值累加起来,然后通过lowbit函数找到x的上一个位置(即最后一个需要累加的位置),再将该位置上的值累加起来,然后继续找上一个位置,直到累加完所有需要累加的位置为止。这样就得到了从位置1到位置x的前缀和。
这就是树状数组的核心操作,通过使用这三个函数,我们可以实现树状数组的各种功能,如求解区间和、单点修改等。
n, MOD, ans = int(input()), 998244353, 1 # 内存69832KB, 时间2847ms
a, fac = list(map(int, input().split())), [1]
tree = [0] * (n + 1)
def lowbit(x):
return x & -x
def update(x, y):
while x <= n:
tree[x] += y
x += lowbit(x)
def getsum(x):
tot = 0
while x:
tot += tree[x]
x -= lowbit(x)
return tot
for i in range(1, n):
fac.append(fac[i-1] * i % MOD)
for i in range(1, n + 1):
cnt = getsum(a[i-1])
update(a[i-1], 1)
ans = (ans + ((a[i-1] - 1 - cnt) * fac[n - i]) % MOD) % MOD
print(ans)线段树(Segment tree)
线段树 segment tree 来计算第i位右边比该数还要小的数的个数。
n, MOD, ans = int(input()), 998244353, 1 # 内存69900KB, 时间5162ms
a, fac = list(map(int, input().split())), [1]
tree = [0] * (2*n)
def build(arr):
# insert leaf nodes in tree
for i in range(n):
tree[n + i] = arr[i]
# build the tree by calculating parents
for i in range(n - 1, 0, -1):
tree[i] = tree[i << 1] + tree[i << 1 | 1]
# function to update a tree node
def updateTreeNode(p, value):
# set value at position p
tree[p + n] = value
p = p + n
# move upward and update parents
i = p
while i > 1:
tree[i >> 1] = tree[i] + tree[i ^ 1]
i >>= 1
# function to get sum on interval [l, r)
def query(l, r):
res = 0
l += n
r += n
while l < r:
if (l & 1):
res += tree[l]
l += 1
if (r & 1):
r -= 1
res += tree[r]
l >>= 1
r >>= 1
return res
#build([0]*n)
for i in range(1, n):
fac.append(fac[i-1] * i % MOD)
for i in range(1, n + 1):
cnt = query(0, a[i-1])
updateTreeNode(a[i-1]-1, 1)
ans = (ans + (a[i-1] -1 - cnt) * fac[n - i]) % MOD
print(ans)【汤立祥 25物理学院 】思路:要推断一个置换(包含
整个算法的核心就是以
class BIT:
def __init__(self, N):
self.N = N
self.BIT = [1] * (N + 1)
for i in range(1, N+1):
j = i + (i & (-i))
if j <= N:
self.BIT[j] += self.BIT[i]
def update(self, index):
while index <= self.N:
self.BIT[index] -= 1
index += index & (-index)
def prefixSum(self, index):
sum = 0
while index:
sum += self.BIT[index]
index -= index & (-index)
return sum
INF = 998244353
N = int(input())
fact = [1]
for _ in range(1, N):
fact.append(fact[-1] * _ % INF)
bit = BIT(N)
arr = list(map(int, input().split()))
rank = 0
for i in range(N):
factorial = fact[N-i-1]
rank += factorial * (bit.prefixSum(arr[i]) - 1) % INF
bit.update(arr[i])
print((rank + 1) % INF)