• LGOJP6280 [USACO20OPEN]Exercise G


    题目地址

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

    题解

    对于某个排列(A),题目的问题其实可以等价于对于每个(i),连一条((i,a_i))的边,求每个环大小的(lcm)。(因为要回到原位必然要绕圈,每个点都恰好绕一圈的步数就是( ext{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
    ", ans);
    	return 0;
    }
    
  • 相关阅读:
    进程与线程的区别
    信号列表详解
    同步与互斥
    互斥锁
    读写锁
    Redis QPS测试
    从分布式锁来看redis和zookpeer!
    JVM虚拟机调参
    log4j.properties配置详解与实例
    生产者消费者(消费者要消费完才能退出)
  • 原文地址:https://www.cnblogs.com/henry-1202/p/13451774.html
Copyright © 2020-2023  润新知