【JZOJ A组】排列

97 篇文章 0 订阅

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。

思路

显然,我们肯定是把它拆成形如p1x1,p2x2,p3x3……大小的环(p是质数)
原因:可以发现,这些数最总经过若干次后便会自己其实就是一直在走环(可能走多次),一周期就是还的大小,那么多个周期的共同周期就是它们的lcm,所以我们应该让所有数互质。

我们可以用DP解决这个问题:f[i][j]为前i个质数,和为j的最大乘积。转移十分暴力,枚举质数即可(注意如果指数为0,那么就没必要占用一个位置)

我们要知道你那个乘积是怎么来的。我们只要用g数组记录一下所用质数和前驱即可

代码

#include<bits/stdc++.h>
using namespace std;

#define N 10077
#define M 1377

bool bz[N];
double f[M][N];
int p[M],a[N],ans[N],Q[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",&Q[i]);
		n=max(n,Q[i]);
	}
	pre();
	dp();
	for(int i=1; i<=T; i++)
	{
		n=Q[i];
		solve();
		find();
		print();
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值