洗牌问题向来都是用到置换群来做。
此题比较综合。
步骤:
1、用dp[ i ][ j ] = max{dp[ i ][ j ],dp[ i - k ][ j -1]/gcd(dp[ i - k ][ j -1],k)*k}。(dp[ i ][ j ]表示 i 分解为j个数字时候的lcm最小公倍数)。
2、然后要有看过潘震皓的《置换群快速幂运算研究与探讨》基础。可知,要把max lcm[ n ]分解为质因数的乘积形式。。。所以,输入n后,对n对应的max lcm进行分解成质因数乘积,然后排序,之所以排序是因为可能出现:7 = 2×2+3.。。2×2要在后面,因为一阶循环后,必定要从小的循环开始,即要从7 = 2*2+3中的3。。。7的结果为:12 2 3 1 5 6 7 4。。。2,3,1就是三阶循环。而非四阶。
3、不要忘记可能存在一阶情况,如:n = 6时候,结果应该为:6 1 3 2 5 6 4。也就是循环阶数依次为:1,2,3。lcm=6。。。按照阶数大小输出序列就正好符合字典序最小的题意。。
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define llg long long
#define N 110
llg dp[N][N],maxlcm[N];
llg factor[N];
int fnum;
llg p[25]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};
llg gcd(llg x,llg y)
{
return (y==0)?x:gcd(y,x%y);
}
void init(int n)
{
int i,j,k,lcm;
memset(dp,0,sizeof(dp));
for(i = 1;i<=n;i++)
dp[i][1]=i;
for(i = 2;i <= n;i++)
for(j = 2;j<=i;j++)
for(k = 1;k < i && i-k >= j-1 ;k++)
{
lcm = dp[i-k][j-1]/gcd(dp[i-k][j-1],k)*k;
if(lcm > dp[i][j]) dp[i][j] = lcm;
}
/*
for(i = 1;i <= n;i++)
{
for(j = 1;j<=i+2;j++)
{
printf(" %d ",dp[i][j]);
}
printf("\n");
}*/
for(i = 1;i<=n;i++)
{
maxlcm[i]=0;
for(j = 1;j<=n; j++)
if(dp[i][j] >= maxlcm[i]) maxlcm[i] = dp[i][j];
}
}
void split(llg n)
{
int i,j,k,tmp;
fnum = 0;//分解成质因数的数目
for(i=0;i<25;i++)
{
if(n%p[i] == 0)
{
factor[fnum] = 1;
while(n % p[i] ==0)
{
factor[fnum]*=p[i];
n = n/p[i];
}
fnum++;
}
}
}
int main()
{
int i,j,k;
init(100);
llg t,n;
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
split( maxlcm[n]);
sort(factor,factor+fnum);
llg sum = 0;
for(i=0;i<fnum;i++)
{
// printf("factor[%d] = %lld\n",i,factor[i]);
sum += factor[i];
// printf("sum = %lld\n",sum);
}
printf("%lld",maxlcm[n]);
// printf("fnum = %d sum = %lld factor[0] = %lld \n",fnum,sum,factor[0]);
/*先输出 一阶变化,按字典序来说,应该为前面几个数字*/
// printf("n = %lld sum = %lld",n,sum);
for(i=1;i<=n-sum;i++)
printf(" %d",i);
int first = n-sum;
for(i=0;i<fnum;i++)
{
for(j=2;j<=factor[i];j++)
printf(" %d",j+first);
printf(" %d",first+1);/*小循环中最小的那个数字*/
first+=factor[i];
}
printf("\n");
}
return 0;
}