BZOJ 1025: [SCOI2009]游戏
1025: [SCOI2009]游戏
Description
windy学会了一种游戏。对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应。最开始windy把数字按顺序1,2,3,……,N写一排在纸上。然后再在这一排下面写上它们对应的数字。然后又在新的一排下面写上它们对应的数字。如此反复,直到序列再次变为1,2,3,……,N。 如: 1 2 3 4 5 6 对应的关系为 1->2 2->3 3->1 4->5 5->4 6->6 windy的操作如下 1 2 3 4 5 6 2 3 1 5 4 6 3 1 2 4 5 6 1 2 3 5 4 6 2 3 1 4 5 6 3 1 2 5 4 6 1 2 3 4 5 6 这时,我们就有若干排1到N的排列,上例中有7排。现在windy想知道,对于所有可能的对应关系,有多少种可能的排数。
Input
包含一个整数,N。
Output
包含一个整数,可能的排数。
Sample Input
【输入样例一】
3
【输入样例二】
10
3
【输入样例二】
10
Sample Output
【输出样例一】
3
【输出样例二】
16
【数据规模和约定】
30%的数据,满足 1 <= N <= 10 。
100%的数据,满足 1 <= N <= 1000 。
3
【输出样例二】
16
【数据规模和约定】
30%的数据,满足 1 <= N <= 10 。
100%的数据,满足 1 <= N <= 1000 。
——我是愉快的分隔符——
这道题嘛,看数字对应的循环节的长度,最后的排数就是所有循环节大小的最大公倍数。
所以这道题就转化为:已知a1+a2+a3+a4+…+ak=n。求LCM(a1,a2,a3,a4,…,ak)有多少种不同大小。
我们可以使用记忆化搜索的方法。先求出n一下的素数,然后用记忆化搜索把n,分解成若干个素数的若干倍。因为素数之间是和关系,要求素数的LCM,所以简单的一想是不会有重复的。设F[i][j]表示试到了第i个素数,n还剩余j。那么很简单发现F[i][j]=sigma{f[i][j-Prime[i]*k,k∈(0,+∞)且Prime[i]*k≤j]}。
下面是代码:
#include<cstdio> using namespace std; const int Maxn=1000; int Prime[Maxn+10]; long long Ans[Maxn+10][Maxn+10]; int N; int n; inline void getPrime(int x){ bool isPrime=true; for (int i=1;i<=N;i++){ if (x%Prime[i]==0){ isPrime=false; break; } } if (isPrime) Prime[++N]=x; } long long getAns(int index,int left){ if (Ans[index][left]>0) return Ans[index][left]; if (index<=0) return 1; Ans[index][left]=getAns(index-1,left); for (int Times=Prime[index];Times<=left;Times*=Prime[index]) Ans[index][left]+=getAns(index-1,left-Times); return Ans[index][left]; } int main(){ Prime[N=1]=2; scanf("%d",&n); for (int i=3;i<=n;i++) getPrime(i); printf("%lld\n",getAns(N,n)); return 0; }