luogu P5367 【模板】康托展开 / P2518 [HAOI2010]计数

今天学习康托展开 和 可重集康托展开。。。

康托展开。。就是给你一个1~n全排列,问它的排名。。

例如:x1, x2 , x3 , x4 , x5 是一个1~5的全排列,现在我们要求它的排名。。

从第1位开始枚举。。

然后枚举j,当然这个j要< xi ,并且不能在x(1~i-1)中出现过。

发现当 j < xi 时 ,后面无论怎样填,都会比所求排列的字典序小。。所以就是! (len-i) 。累加到答案中。

枚举完j后,就令第i位为xi,将xi标记上即可,后面枚举 j 时就不能在枚举到 xi了。

考虑优化枚举 j 这个过程。。

开个树状数组。记录一下前面1~i-1出现过那些数,合法的j的个数即为xi-1-ask(xi - 1)啦,直接乘上 ! (len-i) 即可。。

最后add(xi)。。

然后就是蛤OI的这道计数题啦。。

只不过是可以有相同的元素罢了。。

把上面枚举j统计答案乘!(len-i)的过程改为乘上后面剩余元素可重集的全排列即可。。

其他的大致相同。。这个玩意就不能用树状数组了,只能老老实实枚举 j 。。

由于这个题它TM不取模,,搞得求可重集的全排列的过程很难搞。。

虽然最后结果不会爆long long 但中间也可能会爆。。

所以我们就不能直接按求可重集的全排列的公式来求了。。

用组合数。。而由于我是个蒟蒻,不会用符号,就没有办法演示了。。看代码吧。。

计算组合数要用递推预处理,不然还不如直接搞阶乘上下相消呢(比较麻烦)

P2518计数Code    怎么这么短。。

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
char a[100];
int b[11],len;
ll ans,c[55][55];
ll clac(int x){
    ll res=1;
    for(int i=0;i<=9;i++){
        res*=c[x][b[i]];x-=b[i];
    }
    return res;
}
int main(){
    scanf("%s",a+1);len=strlen(a+1);
    c[0][0]=1;
    for(int i=1;i<=50;i++){
        c[i][0]=1;
        for(int j=1;j<=i;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
    for(int i=1;i<=len;i++) b[a[i]-'0']++;
    for(int i=1;i<=len;i++){
        for(int j=0;j<a[i]-'0';j++){
            if(b[j]){
                b[j]--;
                ans+=clac(len-i);
                b[j]++;
            }
        }
        b[a[i]-'0']--;
    }
    printf("%lld\n",ans);
}

 

posted @ 2019-10-24 10:44  dzzx_Syh  阅读(117)  评论(1编辑  收藏  举报