首页 > 其他分享 >康托展开

康托展开

时间:2023-04-03 20:44:15浏览次数:41  
标签:字符 排列 小于 int ll maxn 展开 康托

  一个老生常谈的问题,给定一个两两不同字符的排列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;
}

 

标签:字符,排列,小于,int,ll,maxn,展开,康托
From: https://www.cnblogs.com/yokino/p/17284345.html

相关文章