1. 程式人生 > 實用技巧 >【佛山市選2013】排列——發現性質與轉化問題

【佛山市選2013】排列——發現性質與轉化問題

排列

一個關於n個元素的排列是指一個從{1,2,…,n}到{1,2,…,n}的一一對映的函式。這個排列p的秩是指最小的k,使得對於所有的i=1,2,…,n,都有p(p(…p(i)…))=i(其中,p一共出現了k次)。
例如,對於一個三個元素的排列p(1)=3,p(2)=2,p(3)=1,它的秩是2,因為p(p(1))=1,p(p(2))=2,p(p(3))=3。
給定一個n,我們希望從n!個排列中,找出一個擁有最大秩的排列。例如,對於n=5,它能達到最大秩為6,這個排列是p(1)=4,p(2)=5,p(3)=2,p(4)=1,p(5)=3。

當我們有多個排列能得到這個最大的秩的時候,我們希望你求出字典序最小的那個排列。對於n個元素的排列,排列p的字典序比排列r小的意思是:存在一個整數i,使得對於所有j<i,都有p(j)=r(j),同時p(i)<r(i)。對於5來說,秩最大而且字典序最小的排列為:p(1)=2,p(2)=1,p(3)=4,p(4)=5,p(5)=3。

輸入的第一行是一個整數T(T<=10),代表資料的個數。
每個資料只有一行,為一個整數N。

對於每個N,輸出秩最大且字典序最小的那個排列。即輸出p(1),p(2),…,p(n)的值,用空格分隔。

輸入:

2

5

14

輸出:

21453

2315674910111213148

對於40%的資料,有1≤N≤100。
對於所有的資料,有1≤N≤10000。

分析:

題目一眼看過去就知道是很多個環,而且再一眼看過去就知道是求環點數的最小公倍數最大時的方案。先不考慮字典序,對於這些環而言,他們的總點數要<=n,因為剩下的一個個單點可以形成子環,不影響最小公倍數。那麼現在問題轉化為一個有限揹包問題:取幾個互質的質數的c次方(有限)使得乘積最大,在dp的過程中我們可以同時記錄對應的這幾個數,少了再填幾個1。現在再來考慮字典序,我們觀察樣例可以發現分成幾個環後,它總是將首位移到這個環後面,例如1,2,3這三點構成的環,它們會形成2,3,1。再結合字典序最小,我們會期望將這幾個環中,環點數少的放在前面,因為每次環中最小的會被放在最後,這樣做能讓小的數儘量往前,也就滿足了字典序最小。

當然這題還要注意的是,由於我們所處理的是幾個數的乘積,在資料給的範圍內是會超long long限制的,而在程式碼實現中我們也只需要比較這些最小公倍數的大小,我們可以找到一個單調遞增並且增速很慢的函式來代替最小公倍數,我們會傾向於選擇ln函式,也就是cmath裡帶的log函式。(這題最大的收穫是這裡,在只有比較大小的情況下,我們為了防爆long long,可以選擇ln來優化)

程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
#define debug printf("zjyvegetable\n")
#define int long long
#define R register
#define ld long double
inline int read(){
    int a=0,b=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')b=-1;c=getchar();}
    while(isdigit(c)){a=a*10+c-'0';c=getchar();}
    return a*b;
}
const int N=1e5+50;
int T,a[20],maxn,cnt,prime[N],ans[N],t[N][101],tot[N];
bool bt[N];
ld f[N];
void find_pri(){
    for(R int i=2;i<=maxn;i++){
        if(!bt[i]){
            prime[++cnt]=i;
        }
        for(R int j=1;j<=cnt&&i*prime[j]<=maxn;j++){
            bt[i*prime[j]]=true;
        }
    }
}
signed main(){
    T=read();
    for(R int i=1;i<=T;i++){
        a[i]=read();
        maxn=max(maxn,a[i]);
    }
    find_pri();
    for(R int i=0;i<=maxn;i++)ans[i]=i;
    for(R int i=1;i<=cnt&&prime[i]<=maxn;i++){
        for(R int j=maxn;j>=prime[i];j--){
            for(int rem=prime[i];rem<=j;rem*=prime[i]){
//                f[j]=max(f[j],f[j-rem]*rem);
                if(f[j-rem]+log((ld)rem)>f[j]){
                    f[j]=f[j-rem]+log((ld)rem);
                    tot[j]=tot[j-rem];
                    for(R int p=1;p<=tot[j-rem];p++)
                    t[j][p]=t[j-rem][p];
                    t[j][++tot[j]]=rem;
                }
            }
        }
    }
    for(R int i=2;i<=maxn;i++){
        if(f[ans[i]]<f[ans[i-1]])ans[i]=ans[i-1];
    }
    int sum,now;
    for(R int i=1;i<=T;i++){
        sum=0;now=0;
        sort(t[ans[a[i]]]+1,t[ans[a[i]]]+tot[ans[a[i]]]+1);
        for(R int j=1;j<=tot[ans[a[i]]];j++)
        sum+=t[ans[a[i]]][j];
        if(sum!=a[i]){
            for(;sum<a[i];sum++)
            printf("%lld ",++now);
        }
        for(R int j=1;j<=tot[ans[a[i]]];j++){
            for(R int k=1;k<t[ans[a[i]]][j];k++)
            printf("%lld ",now+k+1);
            printf("%lld ",now+1);
            now+=t[ans[a[i]]][j];
        }
        printf("\n");
    }
    return 0;
}