排列
题目
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。
解题思路
我们再观察这时候的答案,显然,对于一个排列,最大的秩就是所有环大小的最小公倍数。因此,问题转化成:
把n拆成几个正整数的和(正整数可以为1),这些正整数的最小公倍数就是我们要的最大的秩,而字典序最小的拆法就是我们要输出的东西。
最小公倍数归根到底是很多质数的乘积,因此我们直接用质数来构造。
现在问题变成:
现在有一堆质数(\(n\)以内),每个质数的使用次数上限是已知的(就是上面的\(c\)的大小上限)。我要选择一些质数(或它的幂),使他们的和\(\leq n\),然后这些质数(或它的幂)的乘积要最大。
而这其实就是有限背包问题。
设\(f_{i,j}\)表示我们处理到第\(i\)个质数、当前和为\(j\)所能获得的最大秩。则 \(f_{i,j} = \max(f_{i-1,j-p_i^k}*p_i^k)\) 。而我们要求排列的话,只需记录一下每个状态是由哪个状态转移过来的,最后还原即可。
但是
\(f\)的值很大很大,大过\(long long\),所以我们要把f的值转为自然对数来做。由于自然对数是单调函数,所以比较大小的方式一毛一样。
\(Code\)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int a[100005],vis[10005],n,g[1230][10005],p[10005],tot = 0;
double f[1230][10005];
void init()
{
vis[1] = 1;
for (int i = 2; i <= 10005; i++)
{
if (!vis[i]) p[++tot] = i;
for (int j = 1; j <= tot && p[j] * i <= 10005; j++)
{
vis[p[j] * i] = 1;
if (i % p[j] == 0) break;
}
}
}
int main()
{
int t;
scanf("%d",&t);
init();
while (t--)
{
scanf("%d",&n);
memset(f,0,sizeof(f));
f[0][0] = (double)log(1.0);
int g2;
for (int i = 1; i <= tot; i++)
{
for (int j = n; j >= 1; j--)
{
f[i][j] = f[i - 1][j];
int k = 1;
while (k * p[i] <= j)
{
k = k * p[i];
if (f[i - 1][j - k] + (double)log(k * 1.0) > f[i][j])
f[i][j] = f[i - 1][j - k] + (double)log(k * 1.0),
g[i][j] = k;
}
}
}
if (n == 1)
{
printf("1\n");
continue;
}
int g1;
double ans = 0;
for (int i = 1; i <= n; i++)
if (f[tot][i] > ans) ans = f[tot][i],g1 = i;
int l = g1,j = 0;
for (int i = tot; i; i--)
{
if (g[i][l]) a[++j] = g[i][l];
l -= g[i][l];
}
sort(a + 1,a + 1 + j);
int sum = n - g1 + 1;
for (int i = 1; i < sum; i++)
printf("%d ",i);
for (int i = 1; i <= j; i++)
{
for (int k = sum + 1; k <= sum + a[i] - 1; k++)
printf("%d ",k);
printf("%d ",sum);
sum += a[i];
}
printf("\n");
}
}