【佛山市選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。
輸入的第一行是一個整數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; }