pku 3590 The shuffle Problem

1 篇文章 0 订阅
1 篇文章 0 订阅

洗牌问题向来都是用到置换群来做。

此题比较综合。

步骤:

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;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值