您的位置:首页 > 其它

poj3590 The shuffle Problem(置换+dp)

2018-02-05 07:17 387 查看
题目链接

题目大意:一个置换多次操作后就可以回到最初的状态,这个次数称为置换的循环节。求长度为n的序列的最大的循环节x,并且构造循环节为x的字典序最小的方案。

分析:

这道题是bzoj1025的变式

如果我们已知一个置换

我们通过找到ta的轮换就可以计算出ta的循环节:lcm(size轮换)lcm(size轮换)

我们要使长度为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]表示数ii的最大分割方案

f[i]=max(f[i],lcm(f[j],i−j))f[i]=max(f[i],lcm(f[j],i−j))

再开一个数组gg记录每个状态的转移点,最后就可以倒退得到最大分割方案了

但是为什么WA了呢?

这样dp虽然可以计算出正确的循环节长度

但是得到的分割方案有可能段数较少,长度较大,得到的解不是字典序最小

实际上,题目可以转化为:

给你一个整数n,求a1+a2+a3...+am=na1+a2+a3...+am=n, 并且a1,a2,...,ama1,a2,...,am的最小公倍数最大

我们要保证求得最小公倍数之后置换排序最小

考虑lcmlcm最简单的定义:

lcm(a,b)=pk11∗pk22∗pk33∗...∗pkmmlcm(a,b)=p1k1∗p2k2∗p3k3∗...∗pmkm

其中pipi是指数,ki=max(kai,kbi)ki=max(kai,kbi)

也就是说我们将lcm质因数分解了

pkiipiki都是互素的,所以我们可以把序列分割成长度为pkiipiki的轮换,同时保证∑pkii=n∑piki=n

设计状态:f[i][j]f[i][j]表示使用了前ii个素数,当前pkpk和为jj的lcm最大值

(100以内一共有25个素数;pkpk实际上就是一个轮换的长度,那么jj就是序列的长度了)

转移:f[i][j]=max(f[i−1][j],f[i−1][k]∗prime[i]j−k)f[i][j]=max(f[i−1][j],f[i−1][k]∗prime[i]j−k)

用另一个数组记录每一个状态的转移点

这样我们就可以把lcm分解成长度分别是为pk11,pk22,pk33,...,pkmmp1k1,p2k2,p3k3,...,pmkm的若干轮换

注意

这样就转化成了一个特殊的分组背包

质数pi,p2i,p3i,...,pnipi,pi2,pi3,...,pin当做同组的物品,

对于同组的物品只能选一个或者都不选,总和为nn的最大值

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]
处的到最大循环节

但是有些素数是我们没有用上的,

如果g[i][j]=0g[i][j]=0,就说明这个数没有用

我们在构造解的时候,可能会发现:用的数的pkiipiki之和不到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]
,g[30]
,a
;

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: