【佛山市选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;
}

 

posted @ 2020-08-08 11:14  zjy1412  阅读(124)  评论(0编辑  收藏  举报