初见安~这里是传送门:洛谷P4161 [SCOI2009] 游戏
题解
可以发现的一件事情是,这就是一个置换的题目【废话】每写一行,就相当于是进行了一次自身的置换操作。很明显的是,一个置换进行多次操作后总能回到原本的样子,总是循环的,而我们要求的就是可能有多少种循环的长度。根据置换与轮换的知识,我们可以把任意一个置换写成轮换的形式,例如:
,等式右边就是轮换形式,表示这个置换是这两个循环组合起来的。也就是说,每次操作就相当于是各个循环内部进行一次平移,并且周期就是循环的长度。所以我们要求的整体的循环长度就是各个小循环的长度的最小公倍数。所以问题就转化为:将n表示成,求有多少种ans满足。
现在我们就可以单独考虑lcm的问题了。因为,所以为了方便我们找lcm,我们用n以内的质数来凑,就不用考虑gcd了。换言之, 因为有,其中提供这些质因数的循环的长度的和满足【为什么是小于,因为你让剩下的小循环长度都为1就不影响lcm了】,所以我们可以用dp来计数。
设表示各个的和,也就是对lcm的大小做了贡献的 长度为指数的幂次的 小循环的总长度。我们可以预处理出n以内的质数
,枚举幂次,dp即可。
最后的答案就是。
上代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 1005
using namespace std;
typedef long long ll;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n;
int pri[maxn], tot = 0;
ll f[maxn];
bool vis[maxn];
signed main() {
n = read();
for(int i = 2; i <= n; i++) {//筛质数
if(!vis[i]) pri[++tot] = i, vis[i] = true;
for(int j = 1; j <= tot && i * pri[j] <= n; j++) vis[i * pri[j]] = true;
}
f[0] = 1;//分解为了n个小循环的情况
for(int i = 1; i <= tot; i++) for(int j = n; j >= pri[i]; j--) {
register int tmp = pri[i];//计数,应该还是好理解的
while(tmp <= j) f[j] += f[j - tmp], tmp *= pri[i];
}
for(int i = 1; i <= n; i++) f[0] += f[i];//直接用f[0]计数,开个ans也行
printf("%lld\n", f[0]);
return 0;
}