cf501D. Misha and Permutations Summation(康托展开)

题意:

给定两个 0~n-1 的排列 a 和 b,\(ord_a,ord_b\) 分别表示它们是按字典序第几个排列(0~n!-1)。输出 \((ord_a+ord_b)\% (n!)\) 对应的排列

\(n\le 2e5\)

思路:

先分别康托展开,算出ord,加起来,再逆康托展开变回去。

康托展开长这样:\(X=a_n(n-1)!+a_{n-1}(n-2)!+\cdots +a_1\cdot 0!\)

逆康托展开:

  • 首先如果是十进制的ord,要转成变进制:\(X=(n-1)!q+r\),那么左边第一位是 \(q+1\),再算 \(r/(n-2)!\)
  • 不对,上面的做法太傻逼了,像十进制转二进制一样从低位开始溢出就好了!
  • 然后线段树搞一下。也可以二分+树状数组(nlognlogn)或者树状数组上倍增(nlogn)

为了练手我全用线段树写了,然而码力太弱居然调了好久

取模要怎么处理?不用都化成十进制老老实实对 n! 取模(这样的话,n阶乘又要对谁取模?),一直在康托变进制上做即可。

写个竖式加法,注意从左到右第 i 位是 n+1-i 进制。溢出就不管了,这就相当于取模

const signed N = 2e5 + 3;
int n, a[N], b[N];

#define alltree 1, 1, n
#define mid ((l+r)>>1)
#define ls u<<1
#define rs u<<1|1
#define lside ls, l, mid
#define rside rs, mid+1, r
int sum[N<<2];
void pushup(int u) {
    sum[u] = sum[ls] + sum[rs];
}
void upd(int u, int l, int r, int p) {
    if(l == r) return void(sum[u]++);
    if(p <= mid) upd(lside, p);
    else upd(rside, p);
    pushup(u);
}
int askSum(int u, int l, int r, int p) {
    if(p == 0) return 0;
    if(r <= p) return sum[u];
    if(p <= mid) return askSum(lside, p);
    else return sum[ls] + askSum(rside, p);
}
int askPos(int u, int l, int r, int x) { //严格前面恰有x个0的位置
    if(l == r) return l;
    int left0 = mid-l+1 - sum[ls];
    //若x==left0,应该去右边找
    if(x < left0) return askPos(lside, x);
    else return askPos(rside, x-left0);
}

void kangtuo(int a[], int n) { //康托展开
    for(int i = 1; i <= n; i++) {
        upd(alltree, a[i]); //在a[i]位置+1
        a[i] -= askSum(alltree, a[i]-1) + 1; //减去比它小的用过的
    }
}
void nikangtuo(int a[], int n) { //逆康托展开
    for(int i = 1; i <= n; i++) {
        a[i] = askPos(alltree, a[i]); //前面的数中恰有a[i]个0
        upd(alltree, a[i]);
    }
}

signed main() {
    iofast;
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i], a[i]++;
    for(int i = 1; i <= n; i++) cin >> b[i], b[i]++;

    kangtuo(a, n);
    memset(sum, 0, sizeof sum); //线段树清零
    kangtuo(b, n);
    memset(sum, 0, sizeof sum);

    //做加法,从左到右第i位是n+1-i进制
    for(int i = n, carry = 0; i; i--) {
        a[i] += b[i] + carry;
        carry = 0;
        if(a[i] >= n+1-i) a[i] -= n+1-i, carry = 1;
    }

    nikangtuo(a, n);

    for(int i = 1; i <= n; i++) cout << a[i] - 1 << ' ';
}

posted @ 2022-04-06 22:57  Bellala  阅读(39)  评论(0)    收藏  举报