【佛山市选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
输出:
2 1 4 5 3
2 3 1 5 6 7 4 9 10 11 12 13 14 8
对于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; }