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

题意:

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

n2e5

思路:

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

康托展开长这样:X=an(n1)!+an1(n2)!++a10!

逆康托展开:

  • 首先如果是十进制的ord,要转成变进制:X=(n1)!q+r,那么左边第一位是 q+1,再算 r/(n2)!
  • 不对,上面的做法太傻逼了,像十进制转二进制一样从低位开始溢出就好了!
  • 然后线段树搞一下。也可以二分+树状数组(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 @   Bellala  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示