POJ3590 The shuffle Problem题解(置换群+DP)

题目:POJ3590.
题目大意:要求构造一个长度为 n n n的排列的置换,使得这个置换群能够经过最多次的自乘得到单位置换,输出最多的次数和此时字典序最小的置换.
1 ≤ n ≤ 100 1\leq n\leq 100 1n100.

根据置换的结论,一个置换自乘到单位置换的次数为它拆成的每个轮换长度的lcm,现在问题变成了用若干个和为 n n n的正整数使得它们的lcm最大.

考虑一个DP,设 f [ i ] f[i] f[i]表示若干个数和为 i i i时最大的lcm,发现这个状态不具有最优子结构性质,怎么办?

考虑对于一个数 n n n,构造出来最大的lcm时 n n n必然被分为几个素数幂之和在加上几个 1 1 1的形式.此时我们就可以设 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个素数的幂去分解 j j j时最大的lcm,可以列出方程:
f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] , max ⁡ p i k ≤ j { f [ i − 1 ] [ j − p i k ] ∗ p i k } } f[i][j]=\max \{ f[i-1][j],f[i][j-1],\max_{p_i^{k}\leq j} \{ f[i-1][j-p_i^{k}]*p_i^{k} \} \} f[i][j]=max{f[i1][j],f[i][j1],pikjmax{f[i1][jpik]pik}}

然后我们就得到了最大的lcm,也就是自乘的最大次数了.

现在的问题就是如何构造一组字典序最小的置换了.首先我们把最大的lcm分解成几个素数幂之积的形式,记它们的和为 s u m sum sum.

显然最小字典序的解肯定是前面的越小越好,最好就是长度为 1 1 1的置换,所以我们先填入 n − s u m n-sum nsum个长度为 1 1 1的轮换.

然后就是把几个素数幂从小到大排个序,依次构造长度为这些素数幂大小的轮换即可.

时间复杂度瓶颈在于DP,由于每次转移时随着 k k k的增长 p i k p_i^{k} pik呈指数级增长,所以时间复杂度为 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn).

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=100;

int b[N+9],pr[N+9],cp;

void Sieve(int n){
  for (int i=2;i<=n;++i) b[i]=1;
  for (int i=2;i<=n;++i){
  	if (b[i]) pr[++cp]=i;
  	for (int j=1;j<=cp&&i*pr[j]<=n;++j){
  	  b[i*pr[j]]=0;
  	  if (i%pr[j]==0) break;
  	}
  }
}

LL dp[N+9][N+9];

void Get_dp(){
  for (int i=0;i<=N;++i) dp[0][i]=1;
  for (int i=1;i<=cp;++i){
  	dp[i][0]=1;
    for (int j=1;j<=N;++j){
      dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
	  for (int k=pr[i];k<=j;k*=pr[i])
	    dp[i][j]=max(dp[i][j],dp[i-1][j-k]*k);
    }
  }
}

int n,d[N+9],cd;

void Get_d(LL n){
  cd=0;
  LL now=n;
  for (int i=1;i<=cp;++i)
    if (now%pr[i]==0)
      for (d[++cd]=1;now%pr[i]==0;now/=pr[i]) d[cd]*=pr[i];
}

LL ans=0;

Abigail start(){
  Sieve(N);
  Get_dp();
}

Abigail into(){
  scanf("%d",&n);
}

Abigail work(){
  ans=0;
  for (int i=1;i<=n;++i) ans=max(ans,dp[i][n]);
  Get_d(ans);
  sort(d+1,d+cd+1);
}

Abigail outo(){
  printf("%lld",ans);
  int sum=0;
  for (int i=1;i<=cd;++i) sum+=d[i];
  for (int i=1;i<=n-sum;++i)
    printf(" %d",i);
  int now=n-sum;
  for (int i=1;i<=cd;++i){
  	for (int j=1;j<d[i];++j)
  	  printf(" %d",now+j+1);
  	printf(" %d",now+1);
  	now+=d[i];
  }
  puts("");
}

int main(){
  int T;
  scanf("%d",&T);
  start();
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值