JZOJ3232. 【佛山市选2013】排列

JZOJ3232. 【佛山市选2013】排列

Description

一个关于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。

Output

对于每个N,输出秩最大且字典序最小的那个排列。即输出p(1), p(2),…,p(n)的值,用空格分隔。

Sample Input

2
5
14

Sample Output

2 1 4 5 3
2 3 1 5 6 7 4 9 10 11 12 13 14 8

Data Constraint

对于40%的数据,有1≤N≤100。
对于所有的数据,有1≤N≤10000。

Solution

题目大意

给一个数 n ,按照某一规则操作若干次后,使得这个数列最后变成有序数列。求最小操作且字典序最小的序列。

题解

很显然,我们可以将数列划分为若干个大小互质的环,这种情况下,答案显然就是他们的乘积。因此,问题就转化为,给定一个数n,求若干个和为 n 的互质数的最大乘积。这个可以DP解决,设f[i][j]表示到第 i 个质数,和为j的最大乘积。再记录一个前驱。至于方案如何构造,这个就自己思考一下。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std ;

#define N 10000 + 10
#define M 1300 + 10

bool bz[N] ;
double f[M][N] ;
int P[M] , a[N] , ans[N] , Ask[20] , g[M][N] ;
int T , n ;

void Pre() {
    for (int i = 2 ; i <= n ; i ++ ) {
        if ( !bz[i] ) {
            bz[i] = 1 ;
            P[++P[0]] = i ;
        }
        for (int j = 1 ; j <= P[0] ; j ++ ) {
            if ( i * P[j] > n ) break ;
            bz[i*P[j]] = 1 ;
            if ( i % P[j] == 0 ) break ;
        }
    }
}

void DP() {
    for (int i = 0 ; i < P[0] ; i ++ ) {
        for (int j = 0 ; j <= n ; j ++ ) {
            if ( f[i][j] > f[i+1][j] ) {
                f[i+1][j] = f[i][j] ;
                g[i+1][j] = j ;
            }
            int k = P[i+1] ;
            double del = log(P[i+1]) , t = del ;
            for ( ; k + j <= n ; k *= P[i+1] , t += del ) {
                if ( f[i][j] + t >= f[i+1][j+k] ) {
                    f[i+1][j+k] = f[i][j] + t ;
                    g[i+1][j+k] = j ;
                }
            }
        }
    }
}

void solve() {
    double mx = 0 ;
    int now = n ;
    a[0] = 0 ;
    for (int i = 1 ; i <= n ; i ++ ) 
        if ( f[P[0]][i] > mx ) {
            mx = f[P[0]][i] ;
            now = i ;
        }
    for (int i = 1 ; i <=  n - now ; i ++ ) a[++a[0]] = 1 ;
    int i = P[0] ;
    while ( i ) {
        a[++a[0]] = now - g[i][now] ;
        now = g[i][now] ;
        i -- ;
    }
}

void find() {
    sort( a + 1 , a + a[0] + 1 ) ;
    int w = 0 , k = 1 ;
    while ( !a[k] && k <= P[0] ) k ++ ;
    for (int i = 1 ; i <= n ; i ++ ) {
        if ( i == w + a[k] ) {
            ans[i] = w + 1 ;
            w += a[k] ;
            k ++ ;
        } else ans[i] = i + 1 ;
    }
}

void print() {
    for (int i = 1 ; i < n ; i ++ ) printf( "%d " , ans[i] ) ;
    printf( "%d\n" , ans[n] ) ;
}

int main() {
    scanf( "%d" , &T ) ;
    memset( f , 0 , sizeof(f) ) ;
    memset( g , 0 , sizeof(g) ) ;
    for (int i = 1 ; i <= T ; i ++ ) {
        scanf( "%d" , &Ask[i] ) ;
        n = max( n , Ask[i] ) ;
    }
    Pre() ;
    DP() ;
    for (int i = 1 ; i <= T ; i ++ ) {
        n = Ask[i] ;
        solve() ;
        find() ;
        print() ;
    }
    return 0 ;
}

以上.

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值