[BZOJ 1025] [SCOI2009] 游戏 【DP】
题目链接:BZOJ - 1025
题目分析
显然的是,题目所要求的是所有置换的每个循环节长度最小公倍数的可能的种类数。
一个置换,可以看成是一个有向图,每个点的出度和入度都是1,这样整个图就是由若干个环构成,这些环的长度和为 n 。
因此,就是要求出和为 n 的正整数的最小公倍数的可能情况。
有一个性质:这些正整数中有合数存在的最小公倍数,都可以用全是质数的情况包含。
所以我们只要求出用质数组成的情况就可以了。我们要求的就是,若干个质数,它们的和小于等于 n,它们的最小公倍数情况。
先筛法求出 n 以内的所有素数。然后使用 DP:
f[i][j] 表示前 i 个素数之内,组成的和为 j ,LCM 的种类数。
f[i][j] = f[i - 1][j] + sigma(f[j - Prime[i]^k])
初始状态是 f[0][0] = 1
当一些质数的和不同的时候,它们的积一定不同。
代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int MaxN = 1000 + 5; int n, Top; int Prime[MaxN]; bool isPrime[MaxN]; typedef long long LL; LL Ans; LL f[MaxN][MaxN]; int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) isPrime[i] = true; isPrime[1] = false; Top = 0; for (int i = 2; i <= n; ++i) { if (isPrime[i]) Prime[++Top] = i; for (int j = 1; j <= Top && i * Prime[j] <= n; ++j) { isPrime[i * Prime[j]] = false; if (i % Prime[j] == 0) break; } } f[0][0] = 1; for (int i = 1; i <= Top; ++i) { for (int j = 0; j <= n; ++j) f[i][j] = f[i - 1][j]; for (int j = 0; j <= n; ++j) for (int k = Prime[i]; k <= n - j; k *= Prime[i]) f[i][j + k] += f[i - 1][j]; } Ans = 0; for (int i = 0; i <= n; ++i) Ans += f[Top][i]; printf("%lld\n", Ans); return 0; }