luogu P3014 牛线 (康拓展开及其逆向操作)

luogu P3014 [USACO11FEB]牛线Cow Line

Description
有N头牛,和K个询问
询问关键字为P时,读入一个数x,输出N的第x个全排列;为Q时,读入N个数,求这是第几个全排列

Hint
康托展开(Cantor expansion)
首先推荐一个大佬的博客
康拓展开用于计算一个N位的排列是所有N的排列中的第几个,它的算式是$$X=a_n*(n-1)!+a_{n-1}*(n-2)!+…+a_i*(i-1)!+…+a_1*0!$$
其中$a_i$表示当前元素在所有未出现的元素中的序号(从0开始),并且$a_i∈[0,i) (1<=i<=n)$
其实很好推理,以9的全排列举例:842697513是1-9全排列的第几个?
首先看第一位为8,那么第一位为1-7的全排列都比它小,共有7*8!个。
在第一位为8的情况下,其次看第二位为4,那么第二位为1-3的全排列都比它小,共有1*3*7!个
在第一位为8,第二位为4的情况下,那么第三位为1的全排列都比它小,共有1*1*6!个
在第一位为8,第二位为4,第三位为2的情况下,那么第四位为1-5的全排列都比它小,这里由于第二位4和第三位2已经确定,第四位的可能取值只能为(1,3,5),共有1*1*1*3*5!
在第一位为8,第二位为4,第三位为2,第四位为6的情况下,那么第五位为1-8的全排列都比它小,这里由于第二位为4,第三位为2,第四位为6已经确定,第五位的可能取值只能为(1,3,5,7),共有1*1*1*1*4*4!
对于第i位的可能取值,前i位的数字已经确定,且在全排列中每一个数字只能出现一次,只需要比较i位后面的数字比i位数字小的有几个,即可知道第i位的可能取值
则推出康拓展开式

逆康拓展开
即求N的全排列中的第N个
就是康拓展开的逆运算

const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};    //阶乘表
void decantor(int x, int n) {
    vector<int> v;  // 存放当前可选数
    vector<int> a;  // 所求排列组合
    for(int i=1;i<=n;i++) v.push_back(i);
    for(int i=m;i>=1;i--) {
        int r = x % FAC[i-1];
        int t = x / FAC[i-1];
        x = r;
        sort(v.begin(),v.end());// 从小到大排序 
        a.push_back(v[t]);      // 剩余数里第t+1个数为当前位
        v.erase(v.begin()+t);   // 移除选做当前位的数
    }
}

其实求法和进制转化有异曲同工的感觉呢.
这就是康拓展开逆向考虑应该能理解吧…

code

#include<cstdio>
#include<cstring>
#include<iostream>
#define int long long
using namespace std;

int fac[25]={1};

int n,k,x;
int val[25];
bool vis[25];

void reverse_contor(int x){
    memset(vis,0,sizeof vis);
    x--;
    int j;
    for(int i=1;i<=n;i++){
        int t=x/fac[n-i];
        for(j=1;j<=n;j++){
            if(!vis[j]){
                if(!t) break;
                t--;
            }
        }
        printf("%d ",j);
        vis[j]=1;
        x%=fac[n-i];
    }
    puts("");
}

int contor(int x[]){
    int p=0;
    for(int i=1;i<=n;i++){
        int t=0;
        for(int j=i+1;j<=n;j++){
            if(x[i]>x[j]) t++;
        }
        p+=t*fac[n-i];
    }
    return p+1;
}

signed main(){
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i;
    while(k--){
        char ch;cin>>ch;
        if(ch=='P') scanf("%lld",&x),reverse_contor(x);
        else{
            for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
            printf("%lld\n",contor(val));
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LonelyRyan/p/9105911.html