题目:POJ3590.
题目大意:要求构造一个长度为
n
n
n的排列的置换,使得这个置换群能够经过最多次的自乘得到单位置换,输出最多的次数和此时字典序最小的置换.
1
≤
n
≤
100
1\leq n\leq 100
1≤n≤100.
根据置换的结论,一个置换自乘到单位置换的次数为它拆成的每个轮换长度的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[i−1][j],f[i][j−1],pik≤jmax{f[i−1][j−pik]∗pik}}
然后我们就得到了最大的lcm,也就是自乘的最大次数了.
现在的问题就是如何构造一组字典序最小的置换了.首先我们把最大的lcm分解成几个素数幂之积的形式,记它们的和为 s u m sum sum.
显然最小字典序的解肯定是前面的越小越好,最好就是长度为 1 1 1的置换,所以我们先填入 n − s u m n-sum n−sum个长度为 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;
}