随笔 - 21  文章 - 0  评论 - 0  阅读 - 3274 

  一个老生常谈的问题,给定一个两两不同字符的排列x,问其在所有排列情况中的排名。

  很容易得到第一种暴力思路,先找到其所有字符集合的最小排列,再利用c++的next_permutation()函数不断寻找,直到找到原排列。思路清晰,代码简单,时间复杂度却是高达 O(n!) ,可以解决n<=11的情况(如蓝桥杯某年国赛的一道题),但是n一大就会暴死。

  于是引入优化。设排列长度为n,先考虑原排列的第一个字符x1,设x在原排列中的排名为a1,显然,其存在使得原排序大于所有第一个字符小于x1的排列,这些排列的总数为(a1-1)*(n-1)! 。接下去考虑第二个字符x2,其排名为a2,故而其对答案的贡献为x1在第一位且第二位小于x2的所有排列总数。依此类推,第 i 位字符对答案的贡献为第 i 位右侧小于第 i 位字符个数乘以(n-i)! 。阶乘可以通过预处理得到,问题在于如何寻找第 i 位右侧小于第 i 位字符个数。考虑暴力求解,直接循环求解,时间复杂度为O(n^2),依然不够快。

  继续优化,考虑使用线段树或者树状数组。首先明确问题的优化方向为加速寻找第 i 位右侧小于第 i 位字符个数。考虑离散化建立原排序对应出现次数的数组c,明显初始时c的值全部为1,查询第 i 位右侧小于第 i 位字符个数即为查询c数组下标1到x[i]-1的区间和,每次修改时将c[x[i]]置为0即可,这明显可使用线段树或者树状数组进行维护,因为其本质即单点修改,区间查询。这样,总时间复杂度为O(nlogn)。

例题:P5367 【模板】康托展开

  代码如下:

复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=(1e6)+5;
const int modd=998244353;
ll c[maxn];
ll a[maxn];
ll tree[maxn];
int n;
int lowbit(int x){
    return x&(-x);
}
void update(int i,int x){
    while(i<=n){
        tree[i]=(tree[i]+x)%modd;
        i+=lowbit(i);
    }
}
ll query(int i){
    ll res=0;
    while(i>0){
        res=(res+tree[i])%modd;
        i-=lowbit(i);
    }
    return res;
}
ll f[maxn];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>c[i];
        update(i,1);
    }
    ll ans=1;
    f[1]=1;
    for(int i=2;i<n;i++)f[i]=f[i-1]*i%modd;
    for(int i=1;i<=n;i++){
        ans=(ans+query(c[i]-1)*f[n-i]%modd)%modd;
        update(c[i],-1);
    }
    cout<<ans<<endl;
    return 0;
}
复制代码

 

posted on   雪之下雪乃天下第一  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示