[洛谷P3014][USACO11FEB]牛線Cow Line (康託展開)(數論)

如果在閱讀本文之前對於康託展開沒有了解的同學請戳一下這裏:  簡陋的博客    百度百科

 

題目描述

N(1<=N<=20)頭牛,編號爲1...N,正在與FJ玩一個瘋狂的遊戲。奶牛會排成一行(牛線),問FJ此時的行號是多少。之後,FJ會給牛一個行號,牛必須按照新行號排列成線。

行號是通過以字典序對行的所有排列進行編號來分配的。比如說:FJ有5頭牛,讓他們排爲行號3,排列順序爲:

1:1 2 3 4 5

2:1 2 3 5 4

3:1 2 4 3 5

因此,牛將在牛線1 2 4 3 5中。

之後,奶牛排列爲“1 2 5 3 4”,並向FJ問他們的行號。繼續列表:

4:1 2 4 5 3

5:1 2 5 3 4

FJ可以看到這裏的答案是5。

FJ和奶牛希望你的幫助玩他們的遊戲。他們需要K(1<=K<=10000)組查詢,查詢有兩個部分:C_i將是“P”或“Q”的命令。

如果C_i是'P',則查詢的第二部分將是一個整數A_i(1 <= A_i <= N!),它是行號。此時,你需要回答正確的牛線。

如果C_i是“Q”,則查詢的第二部分將是N個不同的整數B_ij(1 <= B_ij <= N)。這將表示一條牛線,此時你需要輸出正確的行號。

 

輸入格式:

* Line 1: Two space-separated integers: N and K

* Lines 2..2*K+1: Line 2*i and 2*i+1 will contain a single query.

Line 2*i will contain just one character: 'Q' if the cows are lining up and asking Farmer John for their line number or 'P' if Farmer John gives the cows a line number.

If the line 2*i is 'Q', then line 2*i+1 will contain N space-separated integers B_ij which represent the cow line. If the line 2*i is 'P', then line 2*i+1 will contain a single integer A_i which is the line number to solve for. 

輸出格式:

* Lines 1..K: Line i will contain the answer to query i.

If line 2*i of the input was 'Q', then this line will contain a single integer, which is the line number of the cow line in line 2*i+1.

If line 2*i of the input was 'P', then this line will contain N space separated integers giving the cow line of the number in line 2*i+1.

 

下面是正文:

這是一個康託展開的(模板)題

對於n個數的全排列,這個地方我們可以運用 STL 中的next_permunation進行優化

ai代表第i個元素在未出現的元素中是第幾大 即在第i~n位的元素中的rank

對與 2 1 3
對於 2 只有1比它大 所以排第1大;
對於 1 沒有比它大的 所以排第0大 ;
對於 3 沒有和它比較的 所以排第0大;
a3=3,a2=0,a1=0

所以ans=3;

康託展開逆運算
已知某一全排列在所有排列中排第x
可以求得 這個全排列

例如 ans=20; 求它的全排列

第一次 20/(3!) =3……2 說明在第一個元素後面有3個比現在的元素小 這個元素就是4;
第二次 2/(2!) =1……0 說明在這個元素後面有一個比它小 這隻能是2;
第三次 0/(1!) =0……0 說明這個元素後面沒有比它小的 只能是 1;
第四次 0/(0!) =0……0 只剩3了;

簡而言之就是通過不斷地向下除來實現一個降低複雜度

 

那麼我們就可以通過這個來得到他們的序列排序了,不過這裏我們要注意的一點就是這是對於整個序列所進行的一個排序,以及排序之間的空格

 

下面上代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
long long n,m,fac[28],vst[30],a[30],x;
char op;
void init()  //初始化 
{
    for(int i=0;i<=28;i++)  // 這裏到了28就已經十分大了,所以無需再往後運算 
    fac[i]=1;
}
ll contor(ll x[])  //Cantor的運算 
{
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        int tag=0;  //標記爲0 
        for(int j=i+1;j<=n;j++)
        {
            if(x[i]>x[j])
            tag++;  //更新標記 
        }
        ans+=tag*fac[n-i];
    }
    return ans+1;
}
void recontor(ll x)  //Cantor的逆運算 
{
    memset(vst,0,sizeof(vst));
    x--;
    ll k;
    for(int i=1;i<=n;i++)
    {
        ll ans=x/fac[n-i];  //Cantor的實際操作1 
        for(int j=1;j<=n;j++)
        {
            if(!vst[j])
            {
                if(!ans)  //加入ans爲0的話就賦值 
                {
                    k=j;
                    break;  //時間上的優化 
                }
                ans--;
            }
        }
        printf("%d ",k);
        vst[k]=1; //回溯 
        x%=fac[n-i];  //Cantor的實際操作2 
    }
    printf("\n");
}
int main()
{
    scanf("%lld%lld",&n,&m);
    init();
    for(int i=1;i<=n;i++)
    fac[i]=i*fac[i-1];  //康託展開的預處理 
    for(int i=1;i<=m;i++)
    {
        cin>>op;  //判斷題目所給的標記
        if(op=='P')
        {
            scanf("%lld",&x);
            recontor(x);  //康託展開逆運算,進行加進去 
        }
        if(op=='Q')
        {
            for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);  //康託展開運算,運算 
            printf("%lld\n",contor(a));
        }
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章