CodeForces 501D Misha and Permutations Summation

47 篇文章 0 订阅
42 篇文章 0 订阅

题意:

n(2*10^5)个元素的排列有n!种  用Perm(x)表示字典序第x的序列(从0开始)  用Ord(排列y)表示排列y的字典序  现在输入排列p和q  求  Perm([Ord(p)+Ord(q)]%n!)

思路:

容易想到  对于第i位p[i]  如果它是第d小的数字  那么说明比它小的d-1个数字所产生的全排列都已经计数过了

例子  35142  第4位是4  它是第2小的(1和3出现过了)  那么35124这种情况一定已经计数了

因此我们可以分别对于p和q找出序列是排第几的  也就是Ord(p)和Ord(q)  那么根据这个过程的逆过程就可以做出Perm了

现在的难点在于n!这个数字太大了  不能计算

但是我们发现  我们感兴趣的东西都是阶乘!!  因此我们只需要记录(n-1)!  (n-2)!  ……  出现的次数  做完Ord(p)和Ord(q)之后将这个次数数组像模拟高精度一样扫一遍  就可以做出[Ord(p)+Ord(q)]%n!的结果了

我在做“查询第几小的数字”这个功能使用了二分+树状数组  因此复杂度为O(nlog^2n)

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<cstdlib>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long LL;
#define N 200010
#define lowbit(x) (x&(-x))

int n;
int a[N], b[N];
int f[N], c[N];

inline void scand(int &ret) {
    char c;
    ret = 0;
    while ((c = getchar()) < '0' || c > '9')
        ;
    while (c >= '0' && c <= '9')
        ret = ret * 10 + (c - '0'), c = getchar();
}

void add(int x, int y) {
    while (x <= n) {
        c[x] += y;
        x += lowbit(x);
    }
}

int sum(int x) {
    int res = 0;
    while (x) {
        res += c[x];
        x -= lowbit(x);
    }
    return res;
}

void init() {
    memset(c, 0, sizeof (c));
    for (int i = 1; i <= n; i++) add(i, 1);
}

int main() {
    scand(n);
    for (int i = 1; i <= n; i++) {
        scand(a[i]);
        a[i]++;
    }
    for (int i = 1; i <= n; i++) {
        scand(b[i]);
        b[i]++;
    }
    init();
    for (int i = 1; i < n; i++) {
        int les = sum(a[i] - 1);
        f[n - i] += les;
        add(a[i], -1);
    }
    init();
    for (int i = 1; i < n; i++) {
        int les = sum(b[i] - 1);
        f[n - i] += les;
        add(b[i], -1);
    }
    for (int i = 1; i < n; i++) {
        f[i + 1] += f[i] / (i + 1);
        f[i] = f[i] % (i + 1);
    }
    //    printf("F:\n");
    //    for (int i = 1; i <= n; i++)printf("%d ", f[i]);
    //    printf("\n");
    init();
    int sml = 1;
    for (int i = n - 1; i >= 1; i--) {
        int l = 1, r = n, mid, tmp, ans = -1;
        while (l <= r) {
            mid = (l + r) / 2;
            tmp = sum(mid - 1);
            if (tmp <= f[i]) {
                l = mid + 1;
                ans = mid;
            } else {
                //ans = mid;
                r = mid - 1;
            }
        }
        if (ans == -1) {
            ans = sml;
        }
        printf("%d ", ans - 1);
        add(ans, -1);
        while (!c[sml]) sml++;
        //        printf("C:\n");
        //        for (int i = 1; i <= n; i++)printf("%d ", c[i]);
        //        printf("\n");
        //        printf("sml %d\n", sml);
    }
    for (int i = 1; i <= n; i++) {
        if (c[i]) {
            printf("%d\n", i - 1);
            break;
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值