[背包]JZOJ 3232 【佛山市选2013】排列
分析
稍作分析就可以发现这是要把一个正整数分为多个正整数,其最小公倍数最大的正整数组的最小字典序
考虑到排列很难构造,那我们直接构造最小公倍数
它必定是若干个质数的乘积
那么题中的最小公倍数则可以表示为
W=p1^c1*p2^c2*p3^c3…… p是质数 c是质数的指数,p之和不超过n
如果比n小怎么办?直接在排列最前端补足1 2 3 4 5 6……
那么可得f[i][j]为处理了第i个质数,和为j的最大最小公倍数(因为是质数直接乘)
f[i][j]=f[i-1][j-w]*w w为质数或质数的幂
至于答案,只需要用g[i][j]记录一下状态是从哪个状态转移过来的即可
注意,f的值会非常大,可以用自然对数法,也可以直接用double(本题几乎无视精度问题)
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; typedef double ld; const int N=1e4+10; int prime[1310],cnt; bool b[N]; int t,n; int from[1310][N],cunt,a[1310],acnt; ld f[1310][N],mx; void Get_Prime() { for (int i=2;i<=10000;i++) { if (!b[i]) prime[++cnt]=i; int j=2*i; while (j<=10000) { b[j]=1; j+=i; } } } void Get(int last) { int dep=cnt; while (dep) { if (last-from[dep][last]) a[++acnt]=last-from[dep][last]; last=from[dep][last];dep--; } } int main() { Get_Prime(); for (scanf("%d",&t);t;t--) { scanf("%d",&n); if (n<5) { for (int i=2;i<=n;i++) printf("%d ",i); printf("1\n"); continue; } mx=0; for (int i=1;i<=cnt;i++) for (int j=0;j<=n;j++) f[i][j]=0; f[0][0]=1; for (int i=1;i<=cnt;i++) { int l=prime[i]; for (int j=0;j<=n;j++) f[i][j]=f[i-1][j],from[i][j]=j; for (;l<=n;l*=prime[i]) { for (int j=l;j<=n;j++) if (f[i][j]<f[i-1][j-l]*1.0*l) { f[i][j]=f[i-1][j-l]*1.0*l; from[i][j]=j-l; } } } ld mx=0;int last; for (int i=1;i<=n;i++) if (mx<f[cnt][i]) { mx=f[cnt][i]; last=i; } for (int i=1;i<=n-last;i++) printf("%d ",i); cunt=n-last;acnt=0; Get(last); sort(a+1,a+acnt+1); for (int i=1;i<=acnt;i++) { for (int j=2;j<=a[i];j++) printf("%d ",j+cunt); printf("%d ",1+cunt); cunt+=a[i]; } printf("\n"); } }
在日渐沉没的世界里,我发现了你。