题目大意:一个置换多次操作后就可以回到最初的状态,这个次数称为置换的循环节。求长度为n的序列的最大的循环节x,并且构造循环节为x的字典序最小的方案。
分析:
这道题是bzoj1025的变式
如果我们已知一个置换
我们通过找到ta的轮换就可以计算出ta的循环节:
lcm(size轮换)
l
c
m
(
s
i
z
e
轮
换
)
我们要使长度为n的置换的循环节尽可能长
就需要产生一种分割方式,把序列分成若干端(若干轮换),使得所有部分长度的lcm最大
上述问题是可以用dp解决的
已知循环节以及分割方式,我们就可以构造解了
直接贪心的把分割长度从小到大放到原始的递增序列上,错一位即可
example:
a: 1 2 3 4 5
分割方式: 1 2|3 4 5
ans: 2 1 4 5 3
下面我们就讨论一下dp的过程:
一开始我想的比较简单:
f[i]
f
[
i
]
表示数
i
i
的最大分割方案
再开一个数组
g
g
记录每个状态的转移点,最后就可以倒退得到最大分割方案了
但是为什么WA了呢?
这样dp虽然可以计算出正确的循环节长度
但是得到的分割方案有可能段数较少,长度较大,得到的解不是字典序最小
实际上,题目可以转化为:
给你一个整数n,求, 并且
a1,a2,...,am
a
1
,
a
2
,
.
.
.
,
a
m
的最小公倍数最大
我们要保证求得最小公倍数之后置换排序最小
考虑
lcm
l
c
m
最简单的定义:
lcm(a,b)=pk11∗pk22∗pk33∗...∗pkmm
l
c
m
(
a
,
b
)
=
p
1
k
1
∗
p
2
k
2
∗
p
3
k
3
∗
.
.
.
∗
p
m
k
m
其中
pi
p
i
是指数,
ki=max(kai,kbi)
k
i
=
m
a
x
(
k
a
i
,
k
b
i
)
也就是说我们将lcm质因数分解了
pkii
p
i
k
i
都是互素的,所以我们可以把序列分割成长度为
pkii
p
i
k
i
的轮换,同时保证
∑pkii=n
∑
p
i
k
i
=
n
设计状态:
f[i][j]
f
[
i
]
[
j
]
表示使用了前
i
i
个素数,当前和为
j
j
的lcm最大值
(100以内一共有25个素数;实际上就是一个轮换的长度,那么
j
j
就是序列的长度了)
转移:
用另一个数组记录每一个状态的转移点
这样我们就可以把lcm分解成长度分别是为 pk11,pk22,pk33,...,pkmm p 1 k 1 , p 2 k 2 , p 3 k 3 , . . . , p m k m 的若干轮换
注意
这样就转化成了一个特殊的分组背包
质数
pi,p2i,p3i,...,pni
p
i
,
p
i
2
,
p
i
3
,
.
.
.
,
p
i
n
当做同组的物品,
对于同组的物品只能选一个或者都不选,总和为
n
n
的最大值
for 所有的组k
for v=V..0
for 所有的i属于组k
f[v]=max{f[v],f[v-c[i]]+w[i]}
tip
在代码中,如果我们不选一个数:f[i][j]=f[i-1][j];g[i][j]=0;
虽然我们可以在f[tot][n]处的到最大循环节
但是有些素数是我们没有用上的,
如果,就说明这个数没有用
我们在构造解的时候,可能会发现:用的数的
pkii
p
i
k
i
之和不到n,
这时我们就用长度为1的轮换补充即可
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=102;
int sshu[26]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,57,61,67,71,73,79,83,89,97};
int n,f[30][N],g[30][N],a[N];
void solve()
{
memset(f,0,sizeof(f)); f[0][0]=1;
memset(g,0,sizeof(g));
for (int i=1;i<=25;i++)
for (int j=n;j>=0;j--) //倒序
{
f[i][j]=f[i-1][j]; //不用这个素数
int now=sshu[i];
while (j>=now)
{
if (f[i][j]<f[i-1][j-now]*now)
f[i][j]=f[i-1][j-now]*now,g[i][j]=now; //now 这个轮换的长度
now*=sshu[i];
}
}
int ans=0,t=0;
for (int i=n;i>=0;i--)
if (f[25][i]>ans) ans=f[25][i],t=i;
printf("%d ",ans);
int cnt=0,x=25,one,tt=0;
while (t)
{
if (!g[x][t]) x--;
else a[++cnt]=g[x][t],t-=g[x][t],x--,tt+=a[cnt];
}
one=n-tt; //长度为1的轮换
sort(a+1,a+1+cnt);
for (int i=1;i<=one;i++) printf("%d ",i);
int s=one+1;
for (int i=1;i<=cnt;i++)
{
for (int j=1;j<a[i];j++) printf("%d ",j+s);
printf("%d ",s);
s+=a[i];
}
printf("\n");
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
solve();
}
return 0;
}