康拓展开
康托展开
康托展开是用来干这个的:求 \(1\sim N\) 的一个给定全排列在所有 \(1\sim N\) 全排列中的排名。
公式就是这个\(ans = 1 + \displaystyle \sum_{i = 1}^{n} A[i] * (n - i)!\)
\(A[i]\)表示\(i + 1 \sim n\)中的数字比\(i\)上的数字小的个数。
为啥呢?可以这么理解,枚举到第\(i\)位了,那前\(i - 1\)位肯定一样,第\(i\)位上可以填的数就是之前没用过的,比\(i\)上的数字小的数,也就是\(A[i]\),那\(i\)位之后怎么填就都可以了,也就是\((n - i)!\)。
得用树状数组维护一下,不然会\(TLE\)。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e6 + 5, mod = 998244353;
int n, ans, fac, a[N], t[N];
int lowbit(int x) { return x & -x; }
void change(int x) { for(; x <= N - 5; x += lowbit(x)) t[x] ++; }
int query(int x) { int res = 0; for(; x ; x -= lowbit(x)) res += t[x]; return res; }
int main() {
n = read(); fac = 1;
for(int i = 1;i <= n; i++) a[i] = read();
for(int i = n;i >= 1; i--) {
int tmp = query(a[i]);
ans = (ans + 1ll * tmp * fac % mod) % mod;
fac = 1ll * fac * (n - i + 1) % mod;
change(a[i]);
}
printf("%d", ans + 1);
return 0;
}
逆康拓展开
问题是一样的,就是反过来了,给你一个排名,让你求对应的排列。
因为这个排名太大了,题目给了你所有的\(A[i]\),这就好办了。我们开一颗权值线段树,每次找其中的第\(A[i]\)大值,并不断删点就好了。
#include <bits/stdc++.h>
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 5e4 + 5;
int T, n, s;
int t[N << 2];
void build(int o, int l, int r) {
if(l == r) { t[o] = 1; return ; }
build(ls(o), l, mid); build(rs(o), mid + 1, r);
t[o] = t[ls(o)] + t[rs(o)];
}
int query(int o, int l, int r, int k) {
t[o] --; if(l == r) return l;
if(t[ls(o)] < k) return query(rs(o), mid + 1, r, k - t[ls(o)]);
return query(ls(o), l, mid, k);
}
int main() {
T = read();
while(T --> 0) {
n = read(); build(1, 1, n);
for(int i = 1;i <= n; i++) {
s = read();
if(i != n) printf("%d ", query(1, 1, n, s + 1));
if(i == n) printf("%d\n", query(1, 1, n, s + 1)); //坑点
}
}
return 0;
}