排列

题目

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");
	}
}
posted @ 2020-08-07 21:38  RiverSheep  阅读(318)  评论(0编辑  收藏  举报