题解:CF501D Misha and Permutations Summation

CF501D 题解

这题应该不止绿吧……

题面

原题传送门

原题传送门(CF)

思路

相信大家都学过康托展开了吧,应该都知道康托展开的排名为 \(\begin{aligned}\sum_{i=1}^n s_i\times(n-i)!\end{aligned}\),其中 \(\begin{aligned}s_i\gets\sum_{j=i+1}^n [a_j<a_i]\end{aligned}\)

把两个序列的排名都求出来显然是不现实的,可以考虑先求出两个序列的 \(s\),记为 \(s1,s2\),在合并 \(s\),最后再用这题的方法来解题,可以参考同样是我写的题解

那么,该怎么正确的合并呢?直接相加显然是错误的,考虑到 \(s_i\leqslant n-i\),所以可以从后往前加,多余的部分是可以进位到上一个字符的,对于第一位直接模 \(n\) 即可。

形式化的有:

  • \(i\geqslant2,s_{i-1}\gets s_{i-1}+\lfloor\frac{s_i+s1_i+s2_i}{n-i+1}\rfloor,s_i\gets(s1_i+s2_i)\bmod (n-i+1)\)
  • \(i=1,s_1\gets(s_1+s1_i+s2_i)\bmod n\)

形象点,可以把这个过程理解成我们的竖式加法,因为同样的都是可以进位的,十进制逢十进一,这里是只要大于等于 \(n-i+1\) 就要进位。

最后就可以得出 \(s\),然后正常逆向康托即可。

这个很好办,用树状数组加二分,我们可以从前往后一个一个确定 \(a\)

\(S_i\) 就为比 \(i\) 小并且未被确定的数的个数。

在逐步确定 \(a\) 过程中,对于每个 \(i\in\{1,2,\cdots,n\}\),比 \(i\) 小并且未被确定的数的个数满足单调性,于是就可以二分求 \(a_i\),然后标上 \(a_i\) 已被标记(在树状数组中把 \(a_i\) 位置减一)。

时间复杂度为 \(O(n\log^2 n)\),用线段树上二分可以优化到 \(O(n\log n)\),但是笔者太懒(菜)了,只打了树状数组(反正能过就行)。

代码

提交记录

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int MN=2e5+5;
ll n,a[MN],s1[MN],s2[MN],s[MN],t[MN];
void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
ll lowbit(ll x){return x&-x;}
void change(ll x, ll v){while(x<=n){t[x]+=v;x+=lowbit(x);}}
ll query(ll x){ll res=0;while(x){res+=t[x];x-=lowbit(x);}return res;}
ll get(ll x){
    ll l=1,r=n+1,res;
    while(l<r){
        ll mid=l+r>>1,num=query(mid-1);
        if(num>x) r=mid;
        else l=mid+1;
    }
    return l-1;
}
int main(){
    n=read();
    for(int i=1; i<=n; i++) a[i]=read()+1,change(i,1);
    for(int i=1; i<=n; i++){s1[i]=query(a[i]-1);change(a[i],-1);}
    for(int i=1; i<=n; i++) a[i]=read()+1,change(i,1);
    for(int i=1; i<=n; i++){s2[i]=query(a[i]-1);change(a[i],-1);}
    for(int i=n-1; i>=2; i--){
        s[i]+=s1[i]+s2[i];
        if(s[i]>=n-i+1){
            s[i-1]+=s[i]/(n-i+1);
            s[i]%=(n-i+1);
        }
    }
    (s[1]+=s1[1]+s2[1])%=n;
    for(int i=1; i<=n; i++) change(i,1);
    for(int i=1; i<=n; i++){
        a[i]=get(s[i]);
        change(a[i],-1);
    }
    for(int i=1; i<=n; i++) write(a[i]-1),putchar(' ');putchar('\n');
    return 0;
}
posted @ 2025-01-29 16:07  naroto2022  阅读(16)  评论(0)    收藏  举报