Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

    • 一些小问题
    • 二次剩余学习笔记
    • 卡特兰数学习笔记
    • 同余最短路学习笔记
    • 康托展开学习笔记
      • 公式
      • 解析
      • 例题 洛谷P5367
      • 例题 UVA11525
    • 快速傅里叶变换学习笔记
    • 拉格朗日插值学习笔记
    • 拓展欧几里得学习笔记
    • 斐波那契数列学习笔记
    • 斯特林数笔记
    • 本原勾股数组学习笔记
    • 模运算学习笔记
    • 欧拉函数
    • 类欧几里得学习笔记
    • 素性检测与大数分解
    • 素数计数问题
    • 组合数问题
    • 高斯整数学习笔记
  • 动态规划

  • 暴力

  • 图论

  • 数据结构

  • 字符串

  • 几何

  • 博弈论

  • 其他

康托展开学习笔记

# 康托展开

# 公式

对于一个nnn个数字的全排列a0,a1,a2,...an−1a_0,a_1,a_2,...a_{n-1}a0​,a1​,a2​,...an−1​,他的排名为

lessthen(a0)∗(n−1)!+lessthen(a1)∗(n−2)!+⋯+lessthen(an−1)0!+1lessthen(a_0)*(n-1)! + lessthen(a_1)*(n-2)! + \cdots + lessthen(a_{n-1})0! + 1lessthen(a0​)∗(n−1)!+lessthen(a1​)∗(n−2)!+⋯+lessthen(an−1​)0!+1

其中lessthen(ai)lessthen(a_i)lessthen(ai​)为{1,2,...,n}−{a0,a1,...,ai}\{1,2,...,n\}-\{a_0,a_1,...,a_i\}{1,2,...,n}−{a0​,a1​,...,ai​}未出现的数字中,小于xxx的数字有多少个

举例:全排列2,3,12,3,12,3,1,它的排名为1∗2!+1∗1!+0∗0!+1=41*2!+1*1!+0*0!+1=41∗2!+1∗1!+0∗0!+1=4

令Ai=lessthen(ai)∗(n−i−1)!A_i=lessthen(a_i)*(n-i-1)!Ai​=lessthen(ai​)∗(n−i−1)!

公式可表示为ans=A0+A1+...+An−1+1ans=A_0+A_1+...+A_{n-1}+1ans=A0​+A1​+...+An−1​+1

# 解析

考虑全排列{3,2,1}\{3,2,1\}{3,2,1}

第一位为333,小于333且未出现过的数字有222个(111和222),所以有2∗2!2*2!2∗2!种

第二位为222,小于222且未出现过的数字有111个,因此有1∗1!1*1!1∗1!种

第三位为111,有0∗0!0*0!0∗0!种

即排名小于{3,2,1}\{3,2,1\}{3,2,1}的全排列有2×2!+1×1!=52\times 2!+1\times 1!=52×2!+1×1!=5种,所以{3,2,1}\{3,2,1\}{3,2,1}的排名为666

# 例题 洛谷P5367

#include <bits/stdc++.h>
#define ll long long

using namespace std;
const int maxn = 1e6 + 100;
const ll mod = 998244353;
ll fac[maxn] = {1};
int val[maxn], bit[maxn], n;

void add(int pos, int val){
    for(int i=pos; i<=n; i+=(-i)&i) bit[i] += val;
}

int query(int pos){
    int ret = 0;
    for(int i=pos; i>0; i-=(-i)&i) ret += bit[i];
    return ret;
}

signed main() {
    cin >> n;
    for(int i=1; i<=n; i++) fac[i] = fac[i-1] * i % mod;
    for(int i=1; i<=n; i++) cin >> val[i];
    for(int i=1; i<=n; i++) add(i, 1);
    ll ans = 0;
    for(int i=1; i<=n; i++){
        add(val[i], -1);
        ans = (ans + fac[n-i] * query(val[i]) % mod) % mod;
    }
    cout << ans + 1 << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 逆康托展开

以{2,3,4,1}\{2,3,4,1\}{2,3,4,1}为例,n=4,idx=10n=4,idx=10n=4,idx=10

首先小于它的序列有idx−1=9idx-1=9idx−1=9个

  1. 9/3!=1......39/3!=1......39/3!=1......3,说明对于首位a0a_0a0​,未使用的数字中只有111个比它小(此处为111),因此a0=2a_0=2a0​=2
  2. 3/2!=1......13/2!=1......13/2!=1......1,a1=3a_1=3a1​=3
  3. 1/1!=1......01/1!=1......01/1!=1......0,a2=4a_2=4a2​=4
  4. 0/0!=00/0!=00/0!=0,a3=1a_3=1a3​=1

查找未使用数字中的第kkk大可以用线段树/树状数组

# 例题 UVA11525

#include <bits/stdc++.h>

using namespace std;

const int maxn = 5e4 + 10;
int tree[maxn * 4], n;

#define mid ((l+r)>>1)
void build(int l, int r, int rt){
    if(l == r) {tree[rt] = 1; return;}
    build(l, mid, rt<<1), build(mid+1, r, rt<<1|1);
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}

int get(int l, int r, int rank, int rt){
    tree[rt]--;
    if(l == r) return l;
    if(tree[rt<<1] >= rank) return get(l, mid, rank, rt<<1);
    return get(mid+1, r, rank - tree[rt<<1], rt<<1|1);
}

void solve() {
    cin >> n;
    build(1, n, 1);
    for(int i=0, k; i<n; i++){
        cin >> k;
        cout << get(1, n, k + 1, 1) << (i==n-1?'\n':' ');
    }
}

signed main() {
    int t; cin >> t;
    while(t--) solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
上次更新: 2021/02/24, 03:37:30
同余最短路学习笔记
快速傅里叶变换学习笔记

← 同余最短路学习笔记 快速傅里叶变换学习笔记→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式