卧薪尝胆,厚积薄发。
SCOI2009 游戏
Date: Sat Aug 04 10:03:47 CST 2018 In Category: NoCategory

Description:

给定 $n$ ,定义一个置换的排数为 $1$ ~ $n$ 的循环经过这个置换最少 $T$ 次 $(T>0)$ 可以回到原来的序列。求所有可能的排数的数量。
$1\le n \le 1000$

Solution:

首先发现排数就是所有置换的循环节长度的最小公倍数 $+1$ ,于是要求的就是长为 $n$ 的序列中划分成几段的最小公倍数的个数。可以把最小公倍数拆分成许多质数的幂,先筛出来 $n$ 以内所有质数,然后记 $f[i][j]$ 表示前 $i$ 个质数,长为 $j$ 的公倍数个数,转移为 $\begin{align}f[i][j]=\sum_{k=1}^{prime[i]^k\le j}f[i-1][j-k]+f[i-1][j]\end{align}$ ,分别代表在 $lcm$ 中这个质数质数是几,不同指数即有不同 $lcm$ 。
由于没有计算 $1$ , $dp$ 初值为 $f[0][i]=1$

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n;
#define MAXN 1010
typedef long long ll;
ll f[MAXN][MAXN];
bool isprime[MAXN];
int prime[MAXN],tot = 0;
int main()
{
scanf("%d",&n);
for(int i = 2;i <= n;++i)isprime[i] = true;
for(int i = 2;i <= n;++i)
{
if(isprime[i])prime[++tot] = i;
for(int j = 1;j <= tot && i * prime[j] <= n;++j)
{
isprime[i * prime[j]] = false;
if(i % prime[j] == 0)break;
}
}
for(int i = 0;i <= n;++i)f[0][i] = 1;
for(int i = 1;i <= tot;++i)
{
for(int j = 0;j <= n;++j)
{
f[i][j] += f[i - 1][j];
for(int k = prime[i];k <= j;k *= prime[i])
{
f[i][j] += f[i - 1][j - k];
}
}
}
cout << f[tot][n] << endl;
return 0;
}
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡