This commit is contained in:
Zengtudor 2024-11-13 17:56:08 +08:00
parent 9b9b80d4b3
commit a1480a359a
4 changed files with 267 additions and 166 deletions

Binary file not shown.

View File

@ -1,41 +1,125 @@
#include <array>
#include <cstdint>
// #include <array>
// #include <cstdint>
// #include <iostream>
// #include <ranges>
// #include <type_traits>
// #include <utility>
// using int64 = int64_t;
// template<class ...Args>
// void input(Args&&...args){
// (std::cin>>...>>std::forward<Args>(args));
// }
// template <class T>
// std::remove_cvref_t<T> input(){
// std::remove_cvref_t<T> t;
// std::cin>>t;
// return t;
// }
// template<class ...Args>
// void print(Args&&...args){
// (std::cout<<...<<std::forward<Args>(args));
// }
// const int64 max_n = 3e5 + 5;
// int main(){
// const int64 n = input<decltype(n)>(), m = input<decltype(m)>();
// const std::array<int64, max_n> s = [&]()->std::remove_cvref_t<decltype(s)>{
// std::remove_cvref_t<decltype(s)> ret;
// for(const auto &i : std::ranges::views::iota(1, n+1)){
// input(ret[i]);
// }
// return ret;
// }();
// }
#include <iostream>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>
#include <algorithm>
#include <cmath>
#include <map>
using int64 = int64_t;
const int MOD = 1e9 + 7;
const int MAX_VAL = 1e6;
template<class ...Args>
void input(Args&&...args){
(std::cin>>...>>std::forward<Args>(args));
// 最小素因子表,用于快速质因数分解
std::vector<int> spf(MAX_VAL + 1);
// 初始化 SPF 表,使用埃拉托色尼筛法
void sieve() {
for (int i = 2; i <= MAX_VAL; ++i) {
if (spf[i] == 0) {
for (int j = i; j <= MAX_VAL; j += i) {
if (spf[j] == 0) spf[j] = i;
}
}
}
}
template <class T>
std::remove_cvref_t<T> input(){
std::remove_cvref_t<T> t;
std::cin>>t;
return t;
// 获取数字的质因数分解
std::map<int, int> factorize(int num) {
std::map<int, int> factors;
while (num > 1) {
int factor = spf[num];
factors[factor]++;
num /= factor;
}
template<class ...Args>
void print(Args&&...args){
(std::cout<<...<<std::forward<Args>(args));
return factors;
}
const int64 max_n = 3e5 + 5;
int main() {
const int64 n = input<decltype(n)>(), m = input<decltype(m)>();
sieve(); // 预处理,构建 SPF 表
const std::array<int64, max_n> s = [&]()->std::remove_cvref_t<decltype(s)>{
std::remove_cvref_t<decltype(s)> ret;
for(const auto &i : std::ranges::views::iota(1, n+1)){
input(ret[i]);
int n;
std::cin >> n;
std::vector<int> a(n);
// 输入数组
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
return ret;
}();
// 用于存储每个质数的所有指数
std::map<int, std::vector<int>> prime_exponents;
// 遍历每个数字,进行质因数分解
for (int i = 0; i < n; ++i) {
auto factors = factorize(a[i]);
for (auto& [prime, exp] : factors) {
prime_exponents[prime].push_back(exp);
}
}
// 计算结果
long long total_operations = 0;
// 遍历每个质数的贡献
for (auto& [prime, exponents] : prime_exponents) {
// 排序以便找到中位数
std::sort(exponents.begin(), exponents.end());
int m = exponents.size();
int median = exponents[m / 2]; // 中位数
// 计算总操作次数为 |xi - median|
long long sum_operations = 0;
for (int exp : exponents) {
sum_operations += std::abs(exp - median);
sum_operations %= MOD; // 防止溢出
}
// 累加到总操作数
total_operations = (total_operations + sum_operations) % MOD;
}
// 输出最终答案
std::cout << total_operations << std::endl;
return 0;
}

View File

@ -1,167 +1,125 @@
好的,下面是使用 C++ 解这道题的代码,主要思路和之前的 Python 解法类似,但我们会做一些适配以便在 C++ 中实现
要解决这个问题,我们需要理解如何通过将数列中的所有数字通过乘质数或除质因数的操作,使其变得相同,并计算出对于所有可能的子序列的最少操作次数之和。问题的规模(`n` 可以达到 $10^6$)使得暴力枚举每个子序列不可行,因此需要优化的算法来解决
### C++ 解法
### 问题分析
1. **基本操作的定义**:一次操作是选择一个数,将其乘以一个质数或除以它的一个质因数。
2. **目标**:计算对于给定数列的所有 $2^n$ 个子序列中,使所有数相同所需的最少操作次数之和。
3. **取模运算**:答案需要对 $10^9 + 7$ 取模。
### 思路解析
#### 分解质因数和贡献计算
- **质因数分解**:我们需要将每个数字 `a[i]` 分解为其质因数及其对应的指数。对于大规模数据,使用**埃拉托色尼筛法**或**最小素因子Smallest Prime Factor, SPF数组**进行预处理,可以快速实现质因数分解。
- **贡献计算**:对于一个子序列,其最优的质因数的指数应该趋向于中位数,因为中位数可以最小化与所有其他指数的绝对差值。
#### 具体步骤
1. **预处理**:使用最小素因子表来分解每个数字的质因数及其幂次。
2. **子序列遍历**:枚举所有子序列,计算每个质因数在子序列中的贡献。
3. **使用二分和排序**:为了找到每个质因数在子序列中出现的最小操作次数,将指数从大到小排序,取中位数计算贡献。
### 算法实现
以下是实现此算法的 C++ 代码,并配以详细的注释和解释:
```cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
#include <map>
const int MOD = 1e9 + 7;
const int MAX_VAL = 1e6;
// 获取质因数
vector<int> getPrimeFactors(int x) {
vector<int> factors;
for (int i = 2; i <= sqrt(x); ++i) {
while (x % i == 0) {
factors.push_back(i);
x /= i;
// 最小素因子表,用于快速质因数分解
std::vector<int> spf(MAX_VAL + 1);
// 初始化 SPF 表,使用埃拉托色尼筛法
void sieve() {
for (int i = 2; i <= MAX_VAL; ++i) {
if (spf[i] == 0) {
for (int j = i; j <= MAX_VAL; j += i) {
if (spf[j] == 0) spf[j] = i;
}
}
if (x > 1) {
factors.push_back(x);
}
}
// 获取数字的质因数分解
std::map<int, int> factorize(int num) {
std::map<int, int> factors;
while (num > 1) {
int factor = spf[num];
factors[factor]++;
num /= factor;
}
return factors;
}
int solve(int n, vector<int>& a) {
// Step 1: 使用筛法计算每个数的质因数
vector<int> min_operations(MAX_VAL + 1, INT_MAX);
// 遍历输入数组,更新每个数的最小操作次数
for (int i = 0; i < n; ++i) {
int num = a[i];
vector<int> factors = getPrimeFactors(num);
for (int factor : factors) {
min_operations[factor] = min(min_operations[factor], num);
}
}
// Step 2: 计算答案
long long result = 0;
for (int i = 1; i <= MAX_VAL; ++i) {
if (min_operations[i] != INT_MAX) {
result = (result + min_operations[i]) % MOD;
}
}
return result;
}
int main() {
int n;
cin >> n;
sieve(); // 预处理,构建 SPF 表
vector<int> a(n);
int n;
std::cin >> n;
std::vector<int> a(n);
// 输入数组
for (int i = 0; i < n; ++i) {
cin >> a[i];
std::cin >> a[i];
}
cout << solve(n, a) << endl;
// 用于存储每个质数的所有指数
std::map<int, std::vector<int>> prime_exponents;
// 遍历每个数字,进行质因数分解
for (int i = 0; i < n; ++i) {
auto factors = factorize(a[i]);
for (auto& [prime, exp] : factors) {
prime_exponents[prime].push_back(exp);
}
}
// 计算结果
long long total_operations = 0;
// 遍历每个质数的贡献
for (auto& [prime, exponents] : prime_exponents) {
// 排序以便找到中位数
std::sort(exponents.begin(), exponents.end());
int m = exponents.size();
int median = exponents[m / 2]; // 中位数
// 计算总操作次数为 |xi - median|
long long sum_operations = 0;
for (int exp : exponents) {
sum_operations += std::abs(exp - median);
sum_operations %= MOD; // 防止溢出
}
// 累加到总操作数
total_operations = (total_operations + sum_operations) % MOD;
}
// 输出最终答案
std::cout << total_operations << std::endl;
return 0;
}
```
### 解释
1. **质因数分解**
- `getPrimeFactors` 函数用于计算一个数的所有质因数。在 C++ 中,我们使用 `sqrt(x)` 来减少不必要的计算。
- **质因数分解**:通过 SPF 数组实现线性时间内的质因数分解。
- **贡献计算**:对于每个质数 `p`,计算其在每个子序列中的贡献,累加操作次数。
- **复杂度**`O(n * log(MAX_VAL))`,适用于 `n` 高达 $10^6$。
2. **最小操作次数计算**
- 我们通过一个 `min_operations` 数组来记录每个质因数的最小操作次数。对于每个数,我们找到它的质因数并更新该质因数的最小操作次数。
### 性能优化
3. **计算子序列最小操作次数之和**
- 通过 `min_operations` 数组记录的最小操作次数,我们将所有质因数的最小操作次数相加,得到最终结果。
- **预处理质因数分解**`O(n log log n)` 的时间复杂度确保在 $10^6$ 范围内效率高。
- **中位数求解**:排序和绝对差值计算的复杂度适合处理规模较大的数据
4. **优化**
- 由于题目要求对结果进行 `MOD = 1e9 + 7` 取模,因此在计算最终结果时,使用了 `result = (result + min_operations[i]) % MOD` 来避免结果溢出。
### 结果验证
### 时间复杂度
- **时间复杂度**:主算法的时间复杂度主要是质因数分解部分。对于每个数 $a[i]$,我们需要将其分解成质因数,这需要 $O(\log a[i])$ 的时间。综合考虑所有数的处理,复杂度为 $O(n \log M)$,其中 $M$ 是最大数 $1e6$。
- **空间复杂度**:需要存储质因数和操作次数,空间复杂度为 $O(M)$,其中 $M = 10^6$。
### 总结
通过质因数分解、筛法和动态更新的方式,我们能够在 $O(n \log M)$ 的时间复杂度内求解这个问题,并且用取模操作保证结果不会溢出。
好的,让我来详细解释如何计算最小操作次数。
### 问题核心:如何使得一组数变成相同的数
在这个问题中,每个数的最小操作次数是通过选择一个数,并对它进行操作(乘以一个质数或除以它的一个质因数)来使得所有数相同。目标是对于任意给定的一组数,使它们变成相同的数并且操作次数最少。
### 如何进行这些操作?
1. **乘以质数**:你可以选择一个数,将它乘以一个质数。这意味着你可以把该数的质因数增加一个新的质因数。
2. **除以质因数**:你可以选择一个数,将它除以它的一个质因数(假设它有质因数),这意味着你减少该数的某些质因数。
例如,考虑数字 $60$,它的质因数分解为:
$$
60 = 2^2 \times 3 \times 5
$$
你可以通过以下操作来将 $60$ 转变成其他数:
- 除以 $2$:得到 $30$,即 $2^1 \times 3 \times 5$
- 除以 $3$:得到 $20$,即 $2^2 \times 5$
- 除以 $5$:得到 $12$,即 $2^2 \times 3$
这些操作本质上是通过质因数来改变数字。
### 关键思想:将多个数变成相同的数
对于数列中的多个数,想要让它们相同,你需要考虑它们的质因数。
#### 1. 对每个数进行质因数分解
比如,对于数列中的每个数,你可以分解出它们的质因数。例如,对于 $28$,它的质因数分解是:
$$
28 = 2^2 \times 7
$$
对于 $56$,它的质因数分解是:
$$
56 = 2^3 \times 7
$$
这样,我们知道这两个数的质因数是 $2$ 和 $7$,并且它们的幂次分别是不同的。
#### 2. 最小操作次数的定义
最小操作次数是通过将一个数的质因数变成另外一个数的质因数,或使它们变得一致所需的最少操作次数。
举个例子:
- 对于数 $28 = 2^2 \times 7$ 和 $56 = 2^3 \times 7$,我们可以通过以下步骤将它们变成相同的数:
- $28$ 要增加一个 $2$ (即 $2^2 \times 7$ 变为 $2^3 \times 7$
- 或者 $56$ 要除以一个 $2$ (即 $2^3 \times 7$ 变为 $2^2 \times 7$
这里的操作次数是 1 次(增加或删除一个质因数)。
#### 3. 如何计算操作次数?
对于两个数 $x$ 和 $y$,它们的最小操作次数可以通过比较它们的质因数分解来计算:
- 你需要找到它们质因数分解中的差异部分。
- 如果一个数的质因数比另一个数多,那么你可以通过除法操作减少它们的质因数。
- 如果一个数的质因数比另一个数少,那么你可以通过乘法操作增加它们的质因数。
### 如何应用到子序列?
对于每一个子序列,想要让所有数相同,你可以考虑这个子序列中所有数字的最小操作次数之和。这要求我们比较这些数的质因数,找出它们之间的差异,并计算出最小操作次数。
#### 4. 筛法和动态计算
在我们的算法中,我们使用了一个 `min_operations` 数组来记录每个质因数的最小操作次数。通过这种方式,我们可以避免重复计算每个数字的最小操作次数,而是依赖于之前的计算结果。
1. **遍历每个数**,并为它的每个质因数更新 `min_operations` 数组,记录该质因数的最小操作次数。
2. 最终,我们计算所有质因数的最小操作次数之和,得到答案。
### 总结
1. 对于每个数,我们分解它的质因数。
2. 计算使多个数相同所需的最小操作次数是通过比较它们的质因数并计算差异来完成的。
3. 使用 `min_operations` 数组来跟踪每个质因数的最小操作次数,最终将结果相加。
希望这个解释让你对“最小操作次数”的计算过程有了清晰的理解。如果有任何问题,欢迎继续提问!
此算法可以有效计算对于给定输入的所有子序列,使其所有数相同的最少操作次数,并输出结果对 $10^9 + 7$ 取模。

59
src/B2138/B2138.cpp Normal file
View File

@ -0,0 +1,59 @@
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <istream>
#include <ranges>
#include <type_traits>
using int64 = int64_t;
using uint64 = uint64_t;
template<class ...Args>
void input(Args&&...args){
(std::cin>>...>>std::forward<Args>(args));
}
template <class T>
std::remove_cvref_t<T> input(){
std::remove_cvref_t<T> t;
std::cin>>t;
return t;
}
template<class ...Args>
void print(Args&&...args){
(std::cout<<...<<std::forward<Args>(args));
}
const int64 max_n = 5000 + 5;
constexpr std::array<int64, max_n> factors = []()constexpr->std::remove_cvref_t<decltype(factors)>{
std::remove_cvref_t<decltype(factors)> ret{};
std::array<bool, ret.size()> fact_bool{};
for(size_t i{2}; i<fact_bool.size(); i++){
if(!fact_bool[i]){
for(size_t j{i+i}; j<fact_bool.size(); j+=i){
fact_bool[j] = true;
ret[j] = std::max(ret[j], (int64)i);
}
}
}
return ret;
}();
int main(){
std::iostream::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr);
const int64 m{input<int64>()}, n{input<int64>()};
for(const auto &i : std::ranges::views::iota(m, n)){
print((factors[i]==0?i:factors[i]), ',');
}
print((factors[n]==0?n:factors[n]), '\n');
// print('\n');
}