这道题最主要的就是问题的转化,转化完就是一道**题了。
仔细的分析一下,题目中要求“再次出现1,2,3...N的排列”,或者也就是问要进行多少次“置换操作”得到原排列,而我们知道任何置换都是可以写成循环乘积的形式的。
也就是比如对于
1->3 2->4 3->1 4->5 5->2
我们可以化简为
(1 3)(2 4 5)
而对于一个给定置换,问它要多少次才能再次得到原排列,显然只有这个置换分解出的的各个循环的长度是我们关心的。比如对于上例中的置换,我们只需要直到它分解出两个循环,长度为2与3即可断定其只需6次即可得到原排列。也就是所有循环的长度的最小公倍数次。但这不就是暴力了吗?
但我们可以把循环的长度分解为质数之积,那么所有长度的最小公倍数就是每个质数的最大幂之积。(详见唯一分解定理)
所以我们就可以令dp[n][m]表示从第m个质数往后,值至多为n的方案数,然后dp就好了。
OVER
#include<bits/stdc++.h>
#define ll long long
const int N=1e5+5,INF=0x3f3f3f3f;
using namespace std;
int n,k=1,tmp;
int pri[200]={0,2};
ll ans[N];
inline int read()
{
register int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
int main()
{
n=read();
if(n==1)
{
printf("1");
return 0;
}
for(int i=3;i<=n;i+=2)
{
pri[++k]=i;
for(int j=2;j<k;j++)
{
if(i%pri[j]==0)
{
k--;
break ;
}
}
}
ans[0]=1;
for(int i=1;i<=k;i++)
{
for(int j=n;j>=pri[i];--j)
{
tmp=pri[i];
while(tmp<=j)
{
ans[j]+=ans[j-tmp];
tmp*=pri[i];
}
}
}
ll anss=0;
for(int i=1;i<=n;i++) anss+=ans[i];
printf("%lld",anss+1);
return 0;
}

京公网安备 11010502036488号