LGOJP6280 [USACO20OPEN]Exercise G

题目地址

https://www.luogu.com.cn/problem/P6280

题解

对于某个排列\(A\),题目的问题其实可以等价于对于每个\(i\),连一条\((i,a_i)\)的边,求每个环大小的\(lcm\)。(因为要回到原位必然要绕圈,每个点都恰好绕一圈的步数就是\(\text{lcm}\),这样就可以恢复成原排列了)
但是转化后仍然难以处理本题的数据范围,每一个环的大小是任意的。
考虑这道题的特殊性质:因为是排列连边,所以一定只有\(n\)条边,不会有独立的点,一定全都是环,所以环的大小的和就是\(n\)
那么问题可以再次转化为:将\(n\)拆分成若干个数,设\(k\)为这些数的\(lcm\),求所有不同\(k\)值的和。
其次考虑一下\(lcm\)的一个素数表达形式,\(lcm\{x_i\}=\prod {p_i^{\max\{k_i\}}}\),我们可以很自然地想到利用动态规划来计算答案。
不妨设\(f[i][j]\)表示利用前\(i\)个素数,它们的和为\(j\)时的答案。这个形式又很像经典的背包dp的形式,因为空间限制的问题,我们可以借鉴01背包,设\(f[i]\)表示所用的素数和为\(j\)时的答案,转移时倒序转移得到答案即可。
综上,转移方程即为\(f[i]=\sum {f[i-p^k]*p^k}\)
\(num_p\)\([1,n]\)内质数个数,则复杂度为\(O( num_p \cdot n \cdot \log n)\)
代码实现如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+5;
int n, mod, tot, f[N], p[N], v[N];

int main() {
	cin >> n >> mod;
	for(int i = 2; i <= n; ++i) {
		if(!v[i]) p[++tot] = i;
		for(int j = 1; i * p[j] <= n && j <= tot; ++j) {
			v[i * p[j]] = 1;
			if(i % p[j] == 0) break;
		}
	}
	f[0] = 1;
	for(int i = 1; i <= tot; ++i) {
		for(int j = n; j >= p[i]; --j) {
			int P = p[i];
			for(; P <= j;) {
				f[j] = (f[j] + 1LL * f[j - P] * P % mod) % mod;
				P *= p[i];
			}
		}
	}
	int ans = 0;
	for(int i = 0; i <= n; ++i) ans = (ans + f[i]) % mod;
	printf("%d\n", ans);
	return 0;
}
posted @ 2020-08-07 11:35  henry_y  阅读(35)  评论(0编辑  收藏