BZOJ1025 [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,1 <= N <= 1000
Output
包含一个整数,可能的排数。
Sample Input
【输入样例一】
3
【输入样例二】
10
3
【输入样例二】
10
Sample Output
【输出样例一】
3
【输出样例二】
16
3
【输出样例二】
16
题解
首先,我们考虑如果有一个置换(即对应关系),我们把它写成循环,例如题目描述中可以写成
$$(1, 2, 3)(4, 5)(6)$$
即每个括号内循环对应。那么,显而易见的,排数等于其第一次回到初始位置的步数加一,即各循环长度的最小公倍数乘积加一。
那么,题目就转化为:将N分解成一些数,每一个分解求出最小公倍数,求有多少不相同的最小公倍数。
由于将一个数分为互质的两个数会更优(即最小公倍数不会改变且和不会更大),所以我们只需要考虑置换中只有质数的幂和1。
那么,问题就转换成了:将N分解成质数幂和1的和,且各质数最多只出现一个幂,有多少种分法?
这个问题就是裸的分组背包,即每一组物品是$(p, p^2, ..., p^{\lfloor log_pn\rfloor})$
附代码:
#include <cstdio> typedef long long LL; const int N = 1050; bool mark[N]; int cnt, prime[500]; void getPrime(int n) { cnt = 0; for (int i = 2; i <= n; ++i) { if (!mark[i]) prime[cnt++] = i; for (int j = 0; j < cnt && prime[j] * i <= n; ++j) { mark[i * prime[j]] = 1; if (!(i % prime[j])) break; } } } LL f[N]; LL tmp[N]; int main() { int n; scanf("%d", &n); f[0] = 1; getPrime(n); for (int i = 0; i < cnt; ++i) { for (int j = 0; j <= n; ++j) { tmp[j] = 0; for (int k = prime[i]; k <= j; k *= prime[i]) tmp[j] += f[j - k]; } for (int i = 0; i <= n; ++i) f[i] += tmp[i]; } for (int i = 0; i < n; ++i) f[n] += f[i]; printf("%lld\n", f[n]); return 0; }