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

Input
输入的第一行是一个整数T(T <= 10),代表数据的个数。
每个数据只有一行,为一个整数N。
2
5
14

Output
对于每个N,输出秩最大且字典序最小的那个排列。即输出p(1), p(2),…,p(n)的值,用空格分隔。
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个点,让你构造一些环使得每个环的长度的最小公倍数最大的 字典序最小的序列
这就是没有加一些技巧的转化,若是再分析,就是求:
a1+a2+a3+..am=n,求lcm(a1,a2,a3..am)最大
然后我们接着分析:
既然要求是最小公倍数最大,那么我们每个ai一定选不同的质数或质数的幂
为什么呢?若是选了a=x*y,b=y*z。那么它们的最小公倍数就是x*y*z
我还不如a1=x*y,b1=z,那么a+b=x*y+y*z.而a1+b1=x*y+z< a+b。
接着我们就可以dp了。
我们设f[i][j]为前i个质数和为j的最大乘积,转移很暴力,直接枚举质数的幂即可。
然后我们再顺便算出每次决策的j1是什么了。
那么我们便可以得出每个环的长度,
接着排个序,a1< a2< a3..< am对于这些环,我们可以这样构造序列:
{2,3,…a1,1}{a1+2,a1+3..a1+a2,a1+1}..{}
这样明显是最优的。
(因为我们前面越优,一定越优,而第一个不能为1,就让它为2。
而且,1放的位置越前面一定越优,所以要排序)
当然,大家还要考虑若是最优解a1+a2..am< n,那么我们就要填1,在输出注意一下就好了
(比如有1个1,那么就是{1},{a1+2…})

最后大家会怀疑前面的dp的时间复杂度,但是事实上质数只用算到很少。
望有人可以帮忙证明一下,谢谢!

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N=10000;
int n,t;
long double f[130][N+3];
int g[130][N+2],wen[11];
int main(){
    bool bz[N];
    memset(bz,0,sizeof(bz));
    int nu=0,sum=0;
    int pow[N][30];
    for(int i=2;i<=N&&sum<=N;i++)
    if (!bz[i]){ int j;sum+=i;
        for(j=2;j<=N/i;j++) bz[i*j]=1;
        nu++;int st=i;
        for(j=1;st<=N;j++) pow[nu][j]=st,st=st*i;
        pow[nu][0]=j;pow[nu][j]=pow[nu][j-1]*i;
    }
    int a[N];
    scanf("%d",&t);
    int mo=0;
    for(int T=1;T<=t;T++)scanf("%d",&wen[T]),mo=max(mo,wen[T]);
    n=mo;
        for(int i=0;i<=nu-1;i++){
        for (int j=0;j<=n;j++) f[i+1][j]=f[i][j],g[i+1][j]=j;
            for(int j=0;j<=n;j++){
               long long k=pow[i+1][1];
               for(int o=1,k=pow[i+1][1];k+j<=n;o++,k=pow[i+1][o]){
                if (f[i+1][j+k]<f[i][j]+log(k)) 
                f[i+1][j+k]=f[i][j]+log(k),g[i+1][j+k]=j;
                }
            }
        }
    for(int T=1;T<=t;T++){
        int j=wen[T];a[0]=0;
        for(int i=nu;i>=1;i--) {
            if (g[i][j]!=j) 
            a[++a[0]]=j-g[i][j];
            j=g[i][j];
        }
        sort(a+1,a+1+a[0]);
        int an=0;int k=1;
        for(int i=1;i<=a[0];i++) an+=a[i];
        if (an!=wen[T]) {
            for(int i=1;i<=wen[T]-an;i++) printf("%d ",i);
            k+=wen[T]-an;
        }
        for(int i=1;i<=a[0];i++){
            for(int j=k;j<=k+a[i]-2;j++) printf("%d ",j+1);
            printf("%d ",k);k=k+a[i];
        }
        printf("\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值