『USACO』2011 Feb Cow Line (康托展开)

题目链接戳我qwq

题目描述

有N头牛,分别用1……N表示,排成一行。
将N头牛,所有可能的排列方式,按字典顺序从小到大排列起来。
例如:有5头牛 1st: 1 2 3 4 5
2nd: 1 2 3 5 4
3rd: 1 2 4 3 5
4th : 1 2 4 5 3
5th : 1 2 5 3 4
……


现在,已知N头牛的排列方式,求这种排列方式的行号。
或者已知行号,求牛的排列方式。
所谓行号,是指在N头牛所有可能排列方式,按字典顺序从大到小排列后,某一特定排列方式所在行的编号。
如果,行号是3,则排列方式为1 2 4 3 5
如果,排列方式是 1 2 5 3 4 则行号为5

有K次问答,第i次问答的类型,由\(C_i\)来指明,\(C_i\)要么是‘P’要么是‘Q’。
\(C_i\)为P时,将提供行号,让你答牛的排列方式。当\(C_i\)为Q时,将告诉你牛的排列方式,让你答行号。



解题思路

康托展开模板题


康托展开

我们假设数列是1 2 5 3 4

那我们模拟一下计算的方法

\(i=1\)时:\(ans+=0*4!\)

\(i=2\)时:\(ans+=0*3!\)

\(i=3\)时:\(ans+=2*2!\)

\(i=4\)时:\(ans+=0*1!\)

\(i=5\)时:\(ans+=0*0!\)

\(ans=4\)说明前面有4个数列,所以它就是第5个数列。

就是\(a[i]*frac(到i时还有几个比a[i]小的数没有出现)\),最终答案\(+1\)就好。

注意\(0!=1\)


康托逆展开

根据上面的方法,我们倒着做就好。

假设是第5个,那么前面就有4个数列。

\(i=1\)时:\(ans=\frac{4}{frac(4!)}=0\)

\(i=2\)时:\(ans=\frac{4}{frac(3!)}=0\)

\(i=3\)时:\(ans=\frac{4}{frac(2!)}=1\)

\(i=4\)时:\(ans=\frac{0}{frac(1!)}=0\)

\(i=5\)时:\(ans=\frac{0}{frac(0!)}=0\)

所以最终数列就是1 2 4 3 5

每次的被除数是上一次计算的余数,答案是结果向下取整,代表前面有几个数比它小还没有出现过。


#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define ll long long
using namespace std;
int n,k;
ll frac[]={1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,87178291200,1307674368000,20922789888000,355687428096000,6402373705728000,121645100408832000,2432902008176640000};
int a[25];
bool vis[25];
int main(){
    scanf("%d%d",&n,&k);
    for(register int i=1;i<=k;i++){
        char s[10];
        scanf("%s",s);
        if(s[0]=='Q'){
            ll ans=0;
            memset(vis,0,sizeof(vis));
            for(register int j=1;j<=n;j++)scanf("%d",&a[j]);
            for(register int j=1;j<=n;j++){
                vis[a[j]]=1;
                ll cnt=0;
                for(register int k=1;k<a[j];k++){
                    if(!vis[k])cnt++;
                }
                ans+=cnt*frac[n-j];
            }
            printf("%lld\n",ans+1);
        }
        else {
            memset(vis,0,sizeof(vis));
            ll x;
            scanf("%lld",&x);
            x--;
            for(register int j=1;j<=n;j++){
                ll chu=x/frac[n-j],yu=x-chu*frac[n-j];
                int cnt=0;
                for(register int k=1;k<=n;k++){
                    if(!vis[k]){
                        cnt++;
                    }
                    if(cnt==chu+1&&!vis[k]){
                        printf("%d ",k);
                        vis[k]=1;
                        break;
                    }
                }
                x=yu;
            }
            putchar('\n');
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/Fang-Hao/p/9582950.html
今日推荐