update
This commit is contained in:
parent
7d0dac0d49
commit
edcfb1e48f
@ -1,3 +1,105 @@
|
||||
int main(){
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
const int MOD = 1e9 + 7;
|
||||
|
||||
class Union {
|
||||
public:
|
||||
std::vector<int> pt, rk;
|
||||
|
||||
Union(int n) : pt(n), rk(n, 0) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
pt[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
int find(int x) {
|
||||
if (pt[x] != x) {
|
||||
pt[x] = find(pt[x]);
|
||||
}
|
||||
return pt[x];
|
||||
}
|
||||
|
||||
bool merge(int x, int y) {
|
||||
int rx = find(x);
|
||||
int ry = find(y);
|
||||
if (rx != ry) {
|
||||
if (rk[rx] > rk[ry]) {
|
||||
pt[ry] = rx;
|
||||
} else if (rk[rx] < rk[ry]) {
|
||||
pt[rx] = ry;
|
||||
} else {
|
||||
pt[ry] = rx;
|
||||
++rk[rx];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool isTree(int n, const std::vector<std::pair<int, int>>& egs) {
|
||||
if (egs.size() != n - 1) return false;
|
||||
|
||||
Union uf(n);
|
||||
for (const auto& edge : egs) {
|
||||
if (!uf.merge(edge.first, edge.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int root = uf.find(0);
|
||||
for (int i = 1; i < n; ++i) {
|
||||
if (uf.find(i) != root) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int cnt_tree(int n, int p, int tp) {
|
||||
std::vector<std::pair<int, int>> egs;
|
||||
|
||||
}
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
egs.emplace_back(i, i + 1);
|
||||
}
|
||||
|
||||
if (tp == 2) {
|
||||
egs.emplace_back(n - 2, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
if (i % p == 0) {
|
||||
egs.emplace_back(i, n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
int cnt = 0;
|
||||
for (int msk = 0; msk < (1 << egs.size()); ++msk) {
|
||||
std::vector<std::pair<int, int>> sg;
|
||||
for (size_t i = 0; i < egs.size(); ++i) {
|
||||
if (msk & (1 << i)) {
|
||||
sg.push_back(egs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)sg.size() == n - 1 && isTree(n, sg)) {
|
||||
cnt = (cnt + 1) % MOD;
|
||||
}
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int t, tp;
|
||||
std::cin >> t >> tp;
|
||||
|
||||
while (t--) {
|
||||
int n, p;
|
||||
std::cin >> n >> p;
|
||||
std::cout << cnt_tree(n, p, tp) << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
383
src/20241116/U498417.md
Normal file
383
src/20241116/U498417.md
Normal file
@ -0,0 +1,383 @@
|
||||
解决这道题的关键在于理解和计算生成树计数,特别是当 $n$ 的范围可以非常大时(如 $10^{12}$ 量级)。以下是分步思考如何解决这类问题的分解方法:
|
||||
|
||||
### 分析问题结构
|
||||
1. **问题的图结构**:
|
||||
- 当 $tp=1$ 时:图是一个链加额外连接到 $n-1$ 节点的边。即 $(i, i+1)$ 对于 $i=0 \sim n-2$,并且对于每个满足 $i \equiv 0 \pmod p$ 的 $i$,额外有 $(i, n-1)$ 的边。
|
||||
- 当 $tp=2$ 时:图是一个环加额外连接到 $n-1$ 节点的边。即 $(i, i+1)$ 对于 $i=0 \sim n-2$,以及 $(n-2, 0)$,并且对于每个满足 $i \equiv 0 \pmod p$ 的 $i$,额外有 $(i, n-1)$ 的边。
|
||||
|
||||
2. **目标**:计算图的不同生成树个数,结果取模 $10^9+7$。
|
||||
|
||||
### 求解思路
|
||||
在图论中,**矩阵树定理** 是用于计算生成树个数的有效工具。矩阵树定理表明,一个无向图的生成树个数等于它的拉普拉斯矩阵的某个主子式的行列式值。
|
||||
|
||||
- **拉普拉斯矩阵** $L$ 定义为:$L_{ii} = \text{度数}(i)$,$L_{ij} = -1$(当 $i \neq j$ 并且 $i$ 和 $j$ 有边相连时),否则 $L_{ij} = 0$。
|
||||
- 我们需要计算拉普拉斯矩阵删除某一行和列后的行列式值。
|
||||
|
||||
### 子任务分解与思路
|
||||
#### 子任务1-4($n \leq 3000$)
|
||||
- 可以显式构建图的拉普拉斯矩阵并使用高斯消元或其他行列式计算方法来求解。
|
||||
- 对于这种规模的 $n$,算法复杂度 $O(n^3)$ 的方法是可行的。
|
||||
|
||||
#### 子任务5-6($n \leq 10^6$)
|
||||
- 对于较大的 $n$,需要利用图的特性优化:
|
||||
- **链结构**($tp=1$)和 **环结构**($tp=2$)可以通过分析其循环性质来简化计算。
|
||||
- 对于链和环,生成树个数可以用递推公式求解,利用图的周期性结构。
|
||||
|
||||
#### 子任务7-8($n \leq 10^{12}$)
|
||||
- 显式构建矩阵已不可行,需要分析周期性和递推关系。
|
||||
- **递推公式和快速幂**:
|
||||
- 当 $tp=1$ 和 $tp=2$ 时,生成树个数满足一定的递推关系,可以将问题转化为矩阵幂的形式。
|
||||
- 例如,斐波那契数列的生成树计数可以通过递推关系 $T(n) = T(n-1) + T(n-2)$ 表示,可以利用矩阵快速幂 $O(\log n)$ 来求解。
|
||||
|
||||
### 实现细节
|
||||
1. **递推关系**:
|
||||
- 找出生成树个数递推的形式(如斐波那契数列的变种)。
|
||||
- 使用矩阵快速幂求解大规模递推问题。
|
||||
|
||||
2. **模运算**:
|
||||
- 由于结果要取模 $10^9+7$,所有运算都需要加上模运算以防止溢出。
|
||||
|
||||
3. **周期性和简化**:
|
||||
- 对于 $p$ 的倍数和连接 $n-1$ 节点的边,分析对生成树计数的影响,找出简化计算的方法。
|
||||
|
||||
### 示例分析
|
||||
#### 链结构 $tp=1$
|
||||
生成树计数的递推关系与普通链表图类似,需要考虑额外的连接点如何影响计数。
|
||||
|
||||
#### 环结构 $tp=2$
|
||||
利用图的周期性,分析递推关系并将其转化为矩阵乘法问题。
|
||||
|
||||
### 总结
|
||||
- 对于小规模问题,显式计算行列式即可。
|
||||
- 对于中等规模问题,使用优化的递推和模运算。
|
||||
- 对于大规模问题($10^{12}$ 级别),使用矩阵快速幂来高效计算递推关系。
|
||||
|
||||
对于子任务1-4,我们可以显式构建拉普拉斯矩阵并计算其行列式。由于这些子任务中 $n \leq 3000$,可以使用 $O(n^3)$ 的算法来解决,例如高斯消元法。
|
||||
|
||||
### 解题思路
|
||||
1. **构建拉普拉斯矩阵**:
|
||||
- 对于 $tp=1$(链图加边),矩阵中每个 $L_{ii}$ 表示节点 $i$ 的度数,$L_{ij} = -1$ 表示 $i$ 和 $j$ 之间有边。
|
||||
- 对于 $tp=2$(环图加边),构建方式类似,但要考虑 $(n-2, 0)$ 的连接边。
|
||||
|
||||
2. **计算行列式**:
|
||||
- 使用高斯消元法或其他行列式计算算法来求解拉普拉斯矩阵删除一行和一列后的行列式。
|
||||
- 可以固定删除矩阵的最后一行和最后一列(如 $n-1$ 行和列),计算余子式的行列式值。
|
||||
|
||||
### 实现步骤
|
||||
1. **构建拉普拉斯矩阵**:
|
||||
- 初始化一个 $n \times n$ 的矩阵 $L$,所有元素初始为 0。
|
||||
- 填入每个节点的度数到对角线元素 $L_{ii}$。
|
||||
- 对每个相邻节点 $(i, i+1)$ 和环上的边 $(n-2, 0)$,在 $L_{ij}$ 和 $L_{ji}$ 处设置 $-1$。
|
||||
|
||||
2. **修改矩阵以计算行列式**:
|
||||
- 删除一行和一列,通常删除最后一行和一列。
|
||||
- 使用高斯消元法求行列式值。
|
||||
|
||||
3. **模运算**:
|
||||
- 所有计算步骤都需加上模运算($10^9 + 7$)来防止溢出。
|
||||
|
||||
### 代码示例
|
||||
以下是一个简化的 C++ 实现思路:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
const int MOD = 1e9 + 7;
|
||||
|
||||
// 高斯消元求行列式
|
||||
int determinant(std::vector<std::vector<int>>& mat, int n) {
|
||||
int det = 1;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int pivot = i;
|
||||
// 找到最大元素作为主元,进行行交换
|
||||
for (int j = i + 1; j < n; ++j) {
|
||||
if (abs(mat[j][i]) > abs(mat[pivot][i])) {
|
||||
pivot = j;
|
||||
}
|
||||
}
|
||||
if (pivot != i) {
|
||||
std::swap(mat[i], mat[pivot]);
|
||||
det = (MOD - det) % MOD; // 交换行改变符号
|
||||
}
|
||||
|
||||
if (mat[i][i] == 0) return 0; // 行列式为0
|
||||
|
||||
// 用主元归一化
|
||||
det = (1LL * det * mat[i][i]) % MOD;
|
||||
int inv = mod_inverse(mat[i][i], MOD); // 求模逆元
|
||||
|
||||
for (int j = i + 1; j < n; ++j) {
|
||||
int factor = (1LL * mat[j][i] * inv) % MOD;
|
||||
for (int k = i; k < n; ++k) {
|
||||
mat[j][k] = (mat[j][k] - 1LL * factor * mat[i][k] % MOD + MOD) % MOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
return det;
|
||||
}
|
||||
|
||||
// 求逆元,使用快速幂
|
||||
int mod_inverse(int a, int m) {
|
||||
int res = 1;
|
||||
int power = m - 2; // 费马小定理
|
||||
while (power) {
|
||||
if (power & 1) res = 1LL * res * a % m;
|
||||
a = 1LL * a * a % m;
|
||||
power >>= 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int t, tp;
|
||||
std::cin >> t >> tp;
|
||||
while (t--) {
|
||||
int n, p;
|
||||
std::cin >> n >> p;
|
||||
|
||||
// 构建拉普拉斯矩阵 L
|
||||
std::vector<std::vector<int>> L(n, std::vector<int>(n, 0));
|
||||
|
||||
// 填入链或环的边和度数
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
L[i][i]++;
|
||||
L[i + 1][i + 1]++;
|
||||
L[i][i + 1]--;
|
||||
L[i + 1][i]--;
|
||||
}
|
||||
if (tp == 2) { // 如果是环,还需要连接 (n-2, 0)
|
||||
L[n - 2][n - 2]++;
|
||||
L[0][0]++;
|
||||
L[n - 2][0]--;
|
||||
L[0][n - 2]--;
|
||||
}
|
||||
|
||||
// 处理 p 相关的额外边
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (i % p == 0 && i != n - 1) {
|
||||
L[i][i]++;
|
||||
L[n - 1][n - 1]++;
|
||||
L[i][n - 1]--;
|
||||
L[n - 1][i]--;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除最后一行和列(n-1),计算行列式
|
||||
std::vector<std::vector<int>> subMatrix(n - 1, std::vector<int>(n - 1, 0));
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
for (int j = 0; j < n - 1; ++j) {
|
||||
subMatrix[i][j] = L[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
int result = determinant(subMatrix, n - 1);
|
||||
std::cout << result << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 说明
|
||||
- `determinant` 函数用于计算行列式值。
|
||||
- 需要实现求逆元 `mod_inverse` 来处理模运算中的除法。
|
||||
- 构建拉普拉斯矩阵时根据 `tp` 和 `p` 来动态添加边。
|
||||
|
||||
### 复杂度分析
|
||||
- **时间复杂度**:$O(n^3)$,适合 $n \leq 3000$。
|
||||
- **空间复杂度**:$O(n^2)$,用于存储矩阵。
|
||||
|
||||
这种方法适合子任务1-4,因为 $n$ 足够小,使用行列式直接计算生成树个数是可行的。
|
||||
对于子任务1-4,如果你不想使用拉普拉斯矩阵,我们可以尝试基于暴力枚举的方法进行求解,尤其是当 $n$ 的范围较小(如 $n \leq 3000$)时。
|
||||
|
||||
### 基本思路
|
||||
对于较小的 $n$,可以使用以下暴力方法:
|
||||
1. **生成所有可能的边集**。
|
||||
2. **枚举所有可能的生成树**:
|
||||
- 通过深度优先搜索(DFS)或广度优先搜索(BFS)遍历所有连通子图,检查是否为树。
|
||||
3. **统计生成树的数量**。
|
||||
|
||||
### 方法步骤
|
||||
1. **构建图的边集**:
|
||||
- 根据题意构建图的边集,对于 $tp=1$ 和 $tp=2$,确保包含链和环结构上的边以及与 $n-1$ 的连接边。
|
||||
|
||||
2. **枚举所有连通的子图**:
|
||||
- 使用组合或递归来枚举 $n-1$ 条边的集合。
|
||||
- 判断子图是否为树(需满足连通且无环)。
|
||||
|
||||
3. **判断树结构**:
|
||||
- 使用并查集(Union-Find)或 DFS/BFS 检查连通性并验证是否有环。
|
||||
|
||||
### 暴力算法实现示例
|
||||
以下是一个 C++ 实现的简化思路:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
const int MOD = 1e9 + 7;
|
||||
|
||||
// 并查集结构,用于判断连通性
|
||||
class UnionFind {
|
||||
public:
|
||||
std::vector<int> parent, rank;
|
||||
|
||||
UnionFind(int n) : parent(n), rank(n, 0) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
parent[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
int find(int x) {
|
||||
if (parent[x] != x) {
|
||||
parent[x] = find(parent[x]);
|
||||
}
|
||||
return parent[x];
|
||||
}
|
||||
|
||||
bool unite(int x, int y) {
|
||||
int rootX = find(x);
|
||||
int rootY = find(y);
|
||||
if (rootX != rootY) {
|
||||
if (rank[rootX] > rank[rootY]) {
|
||||
parent[rootY] = rootX;
|
||||
} else if (rank[rootX] < rank[rootY]) {
|
||||
parent[rootX] = rootY;
|
||||
} else {
|
||||
parent[rootY] = rootX;
|
||||
++rank[rootX];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 检查边集合是否构成树
|
||||
bool isTree(int n, const std::vector<std::pair<int, int>>& edges) {
|
||||
if (edges.size() != n - 1) return false; // 树需要正好有 n-1 条边
|
||||
|
||||
UnionFind uf(n);
|
||||
for (const auto& edge : edges) {
|
||||
if (!uf.unite(edge.first, edge.second)) {
|
||||
return false; // 检测到环
|
||||
}
|
||||
}
|
||||
|
||||
// 检查连通性
|
||||
int root = uf.find(0);
|
||||
for (int i = 1; i < n; ++i) {
|
||||
if (uf.find(i) != root) return false; // 不连通
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int t, tp;
|
||||
std::cin >> t >> tp;
|
||||
|
||||
while (t--) {
|
||||
int n, p;
|
||||
std::cin >> n >> p;
|
||||
|
||||
// 构建边集合
|
||||
std::vector<std::pair<int, int>> edges;
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
edges.emplace_back(i, i + 1);
|
||||
}
|
||||
if (tp == 2) {
|
||||
edges.emplace_back(n - 2, 0); // 环边
|
||||
}
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
if (i % p == 0) {
|
||||
edges.emplace_back(i, n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 枚举所有可能的生成树
|
||||
int count = 0;
|
||||
std::vector<int> indices(edges.size());
|
||||
std::iota(indices.begin(), indices.end(), 0);
|
||||
do {
|
||||
std::vector<std::pair<int, int>> subgraph;
|
||||
for (int i = 0; i < n - 1; ++i) {
|
||||
subgraph.push_back(edges[indices[i]]);
|
||||
}
|
||||
if (isTree(n, subgraph)) {
|
||||
count = (count + 1) % MOD;
|
||||
}
|
||||
} while (std::next_permutation(indices.begin(), indices.end()));
|
||||
|
||||
std::cout << count << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
- **复杂度**:暴力方法的复杂度是指数级的,适合 $n$ 较小的情况(如 $n \leq 10$)。
|
||||
- **优化方向**:对于更大范围的 $n$,需要用组合数学或矩阵树定理进行优化。
|
||||
|
||||
这个方法适合处理子任务1和2,对于子任务3和4($n \leq 3000$),这种方法会超时。
|
||||
|
||||
在分析 $tp = 1$ 和 $tp = 2$ 的特性时,我们可以观察到一些有助于优化解法的图结构性质。以下是每种情况的详细分析:
|
||||
|
||||
### 1. **$tp = 1$ 的特性**(链状图)
|
||||
- **基本结构**:构成一条从 $0$ 到 $n-1$ 的链。链上的每个节点 $(i, i+1)$ 连有一条边。
|
||||
- **特殊边**:对于满足 $i \equiv 0 \pmod{p}$ 的节点 $i$,会有一条边 $(i, n-1)$,即这些节点额外与 $n-1$ 直接连接。
|
||||
- **特性**:
|
||||
- 图中 $0 \sim n-2$ 是一个固定的线性链,因此该部分天然连通。
|
||||
- 当 $p = 1$ 时,$n-1$ 与所有节点都有直接边连接,形成一个星状图。
|
||||
- 当 $p > 1$ 时,仅部分节点与 $n-1$ 连接,这些节点是间隔为 $p$ 的节点。
|
||||
- 这种结构使得我们可以将问题分解为在链基础上添加边 $(i, n-1)$ 后能形成多少不同的生成树。
|
||||
|
||||
### 2. **$tp = 2$ 的特性**(环状图)
|
||||
- **基本结构**:构成一个从 $0$ 到 $n-2$ 并回到 $0$ 的环。每个节点 $(i, i+1)$ 有边,且 $(n-2, 0)$ 有一条边。
|
||||
- **特殊边**:同样,对于满足 $i \equiv 0 \pmod{p}$ 的节点 $i$,会有一条边 $(i, n-1)$。
|
||||
- **特性**:
|
||||
- 环状图在不移除任何边时是连通的,有 $n$ 条边(形成一个闭合环)。
|
||||
- 去掉任意一条边,环就变成了一个生成树。因此可以通过去掉环上的边再加上 $(i, n-1)$ 来构建不同的生成树。
|
||||
- 当 $p = 1$ 时,$n-1$ 会与所有节点相连,此时图结构类似于一个全连接的核心节点。
|
||||
- 当 $p > 1$ 时,仅某些节点与 $n-1$ 连接,形成特定的连接模式。
|
||||
|
||||
### **总结特性**:
|
||||
- **共性**:
|
||||
- 都涉及到将部分节点与 $n-1$ 相连形成新的结构。
|
||||
- 计算生成树时,考虑了如何通过移除多余的边来形成合法的生成树。
|
||||
- **差异**:
|
||||
- $tp = 1$ 时是基于链的添加,图结构更简单。
|
||||
- $tp = 2$ 时是基于环的添加,环的性质导致删除边时的选择更多。
|
||||
|
||||
### **利用这些特性优化解法**:
|
||||
- **连通性分析**:对于 $tp = 1$,在已经连通的链基础上添加 $(i, n-1)$ 边时,确保树结构只需验证这些添加边是否引入了环或冗余边。
|
||||
- **环的边计数**:对于 $tp = 2$,可以利用环中去除边的方法来快速生成生成树(去掉一条边后必定连通)。
|
||||
- **组合数学**:通过对边的组合、计数、特定节点的度数来简化生成树数量的计算。
|
||||
|
||||
这些特性和性质有助于理解如何构建或优化生成树的计数方法,避免直接使用拉普拉斯矩阵。
|
||||
在这段代码中:
|
||||
|
||||
1. **`std::vector<int> indices(edges.size());` 和 `std::iota(indices.begin(), indices.end(), 0);`**:
|
||||
- 创建了一个索引数组 `indices`,用于表示图中边的下标,从 `0` 到 `edges.size() - 1`。`std::iota` 初始化 `indices` 为 `[0, 1, 2, ..., edges.size() - 1]`。
|
||||
|
||||
2. **`do { ... } while (std::next_permutation(indices.begin(), indices.end()));`**:
|
||||
- 使用 `std::next_permutation` 生成 `indices` 所有可能的排列。每个排列表示选取不同的边组合来构建一个子图。
|
||||
- 对每个排列,选取前 `n-1` 条边,构成候选的生成树子图。
|
||||
|
||||
3. **构建子图并验证**:
|
||||
- 将排列中前 `n-1` 条边加入 `subgraph` 中,形成候选的生成树。
|
||||
- 调用 `isTree(n, subgraph)` 检查该子图是否是树:
|
||||
- 确保边数为 `n-1`,满足树的基本条件。
|
||||
- 使用并查集或其他连通性检查算法验证是否连通且无环。
|
||||
|
||||
4. **计数生成树**:
|
||||
- 如果 `subgraph` 被验证为树,增加计数 `count`,并对 `MOD` 取模,保持结果在合理范围内。
|
||||
|
||||
### 原理说明
|
||||
- **组合生成**:这段代码尝试在给定的图的边集中,枚举所有可能的 `n-1` 条边组合。这些组合被验证是否构成一棵生成树(连通且无环)。
|
||||
- **`std::next_permutation`** 用于生成所有可能的排列,确保不同的边组合被逐一尝试。
|
||||
- **生成树定义**:树是一个连通的无环图,且有恰好 `n-1` 条边。该代码通过枚举并验证子图满足树的定义来统计生成树的数量。
|
||||
|
||||
### 复杂度
|
||||
这种暴力方法尝试对所有可能的边组合进行验证。由于 `std::next_permutation` 生成排列的复杂度为 `O(m!)`(`m` 是边数),对于稍大规模的 `m`,运行时间会迅速增加,因此此方法仅适用于较小的图。
|
49
src/20241116/U498420.cpp
Normal file
49
src/20241116/U498420.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <istream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
using ll = int64_t;
|
||||
using std::cin, std::cout;
|
||||
|
||||
const ll maxn = 1e5+5;
|
||||
ll n, s[maxn];
|
||||
|
||||
std::map<ll, std::vector<ll>> hss;
|
||||
|
||||
int main(){
|
||||
std::iostream::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
|
||||
cin>>n;
|
||||
for(ll i{1};i<=n;i++){
|
||||
cin>>s[i];
|
||||
}
|
||||
for(ll i{1};i<=n;i++){
|
||||
ll a;
|
||||
cin>>a;
|
||||
if(auto it {hss.find(s[i])}; it==hss.end()){
|
||||
hss.emplace(s[i], std::vector<ll>{a});
|
||||
}else{
|
||||
it->second.emplace_back(a);
|
||||
}
|
||||
}
|
||||
for(ll i{1};i<=n;i++){
|
||||
ll cnta{};
|
||||
ll maxs{};
|
||||
ll ni{i};
|
||||
auto nowm = hss.begin();
|
||||
auto nows = hss.begin()->second.begin();
|
||||
while(ni--){
|
||||
// cout<<"nows: "<<*nows<<'\n';
|
||||
cnta+=*nows;
|
||||
maxs = (*nowm).first;
|
||||
// cout<<"mxs: "<<maxs<<'\n';
|
||||
if((++nows)==(*nowm).second.end()){
|
||||
++nowm;
|
||||
nows = nowm->second.begin();
|
||||
}
|
||||
}
|
||||
cout<<(maxs*2+cnta)<<'\n';
|
||||
}
|
||||
}
|
59
src/20241116/U498420.md
Normal file
59
src/20241116/U498420.md
Normal file
@ -0,0 +1,59 @@
|
||||
这个问题本质上是一个最小化疲劳值的优化问题,我们可以通过以下步骤来构建解决思路。
|
||||
|
||||
### 问题分析
|
||||
|
||||
阿明的目标是推销产品给螺丝街上的住户。对于每个 `X`(即推销的住户数),我们需要计算在不走多余路程的前提下,他最少能积累多少疲劳值。对于每个住户,有两个因素影响疲劳值:
|
||||
1. **走路的疲劳值**:阿明从入口出发,到每家住户的距离是 `S_i`,他需要走这些距离来到住户家中。
|
||||
2. **推销的疲劳值**:向第 `i` 家住户推销产品需要累积的疲劳值是 `A_i`。
|
||||
|
||||
在每个 `X` 的情况下,阿明需要选择哪些住户,使得他的总疲劳值最小。对于某个 `X`,阿明从入口走到 `X` 个住户,再返回原点。为了最小化总疲劳值,我们需要选择 `X` 个住户,使得走路和推销的疲劳之和最小。
|
||||
|
||||
### 思路
|
||||
|
||||
1. **排序住户的距离**:因为住户的距离 `S_i` 是已经排序过的,所以下标越小的住户越靠近入口。如果阿明想要走最少的路,显然应该选择距离较近的住户。
|
||||
|
||||
2. **选择住户的顺序**:对于 `X` 个住户,显然阿明应该选择前 `X` 个距离最近的住户。对于每个 `X`,我们需要计算阿明的总疲劳值。
|
||||
|
||||
3. **计算疲劳值**:对于每个 `X`:
|
||||
- 走路的疲劳值:阿明走到第 `X` 个住户的距离是 `S[X-1]`,然后再返回,所以路程总是 `2 * S[X-1]`。
|
||||
- 推销的疲劳值:阿明需要推销给这 `X` 个住户的总疲劳值是前 `X` 个住户的推销疲劳值之和。
|
||||
|
||||
### 具体步骤
|
||||
|
||||
1. **计算前 `X` 个住户的推销疲劳值之和**:可以通过前缀和来快速计算累积的推销疲劳值。
|
||||
2. **遍历每个 `X`,计算最小疲劳值**:对于每个 `X`,计算他走的路程和推销的疲劳值之和。
|
||||
|
||||
### 伪代码
|
||||
|
||||
```python
|
||||
# 输入
|
||||
N = int(input())
|
||||
S = list(map(int, input().split()))
|
||||
A = list(map(int, input().split()))
|
||||
|
||||
# 计算前缀和
|
||||
prefix_sum_A = [0] * (N + 1)
|
||||
for i in range(N):
|
||||
prefix_sum_A[i + 1] = prefix_sum_A[i] + A[i]
|
||||
|
||||
# 输出每个X的最小疲劳值
|
||||
for X in range(1, N + 1):
|
||||
# 走路的疲劳值是 2 * S[X-1]
|
||||
# 推销的疲劳值是前 X 家住户的疲劳值之和
|
||||
total_fatigue = 2 * S[X - 1] + prefix_sum_A[X]
|
||||
print(total_fatigue)
|
||||
```
|
||||
|
||||
### 解释
|
||||
|
||||
1. **前缀和的计算**:`prefix_sum_A[i]` 存储的是前 `i` 个住户推销产品的总疲劳值,这样我们可以在常数时间内计算任意区间的推销疲劳值之和。
|
||||
2. **遍历每个 `X`**:对于每个 `X`,我们计算走路的疲劳值和推销的疲劳值之和,并输出结果。
|
||||
|
||||
### 复杂度分析
|
||||
|
||||
- **时间复杂度**:计算前缀和的时间复杂度是 `O(N)`,然后遍历每个 `X` 计算疲劳值的时间复杂度是 `O(N)`,因此总的时间复杂度是 `O(N)`。
|
||||
- **空间复杂度**:我们需要额外的空间存储前缀和,空间复杂度是 `O(N)`。
|
||||
|
||||
### 总结
|
||||
|
||||
这个问题通过排序住户的距离,并使用前缀和来高效计算每个 `X` 对应的疲劳值,从而找出最小的疲劳值。
|
5
src/20241116/sales/sales1.ans
Normal file
5
src/20241116/sales/sales1.ans
Normal file
@ -0,0 +1,5 @@
|
||||
3
|
||||
7
|
||||
12
|
||||
18
|
||||
25
|
3
src/20241116/sales/sales1.in
Normal file
3
src/20241116/sales/sales1.in
Normal file
@ -0,0 +1,3 @@
|
||||
5
|
||||
1 2 3 4 5
|
||||
1 2 3 4 5
|
5
src/20241116/sales/sales2.ans
Normal file
5
src/20241116/sales/sales2.ans
Normal file
@ -0,0 +1,5 @@
|
||||
7
|
||||
11
|
||||
16
|
||||
22
|
||||
27
|
3
src/20241116/sales/sales2.in
Normal file
3
src/20241116/sales/sales2.in
Normal file
@ -0,0 +1,3 @@
|
||||
5
|
||||
1 2 2 4 5
|
||||
5 4 3 4 1
|
100
src/20241116/sales/sales3.ans
Normal file
100
src/20241116/sales/sales3.ans
Normal file
@ -0,0 +1,100 @@
|
||||
3977198
|
||||
4169101
|
||||
4460821
|
||||
4731972
|
||||
7202244
|
||||
9055505
|
||||
9486259
|
||||
13296843
|
||||
14670095
|
||||
15804864
|
||||
17498246
|
||||
19075382
|
||||
19078338
|
||||
19621148
|
||||
20828784
|
||||
21776746
|
||||
22250815
|
||||
24648505
|
||||
30620307
|
||||
31329799
|
||||
31411087
|
||||
33429787
|
||||
41059848
|
||||
41268835
|
||||
41270626
|
||||
43246970
|
||||
44408198
|
||||
44918461
|
||||
54525749
|
||||
56532525
|
||||
58583113
|
||||
60080293
|
||||
60943178
|
||||
64929282
|
||||
70042337
|
||||
71785319
|
||||
73977333
|
||||
78201847
|
||||
78477303
|
||||
79837680
|
||||
80763779
|
||||
81332696
|
||||
82761463
|
||||
84491641
|
||||
84902102
|
||||
85222828
|
||||
88291100
|
||||
88423566
|
||||
88441902
|
||||
89887020
|
||||
91763761
|
||||
92106702
|
||||
93195493
|
||||
93202170
|
||||
102311885
|
||||
105618715
|
||||
108550431
|
||||
109865048
|
||||
111265952
|
||||
111589721
|
||||
112411933
|
||||
112892677
|
||||
113627269
|
||||
120261717
|
||||
121646693
|
||||
128461915
|
||||
128639893
|
||||
130651105
|
||||
130669917
|
||||
131532747
|
||||
132480213
|
||||
134245184
|
||||
139265392
|
||||
139268412
|
||||
139836900
|
||||
143753412
|
||||
144456804
|
||||
144724832
|
||||
148604342
|
||||
150836402
|
||||
151644662
|
||||
152532102
|
||||
153488158
|
||||
156264138
|
||||
157507392
|
||||
159742432
|
||||
162077560
|
||||
162184622
|
||||
163472978
|
||||
165922704
|
||||
166503620
|
||||
168118120
|
||||
169708119
|
||||
170369629
|
||||
181965243
|
||||
183577709
|
||||
183864374
|
||||
185694162
|
||||
197877648
|
||||
198105964
|
3
src/20241116/sales/sales3.in
Normal file
3
src/20241116/sales/sales3.in
Normal file
@ -0,0 +1,3 @@
|
||||
100
|
||||
1988235 2084001 2229841 2364931 3599731 4526081 4741341 6646311 7332597 7899486 8745791 9533983 9535455 9806521 10410299 10883969 11120726 12319075 15304751 15659027 15699577 16708685 20523672 20628068 20628661 21616531 22197127 22451905 27255419 28258759 29283969 30032559 30463593 32456215 35012665 35884118 36979833 39091645 39228883 39909041 40372036 40656224 41370535 42235456 42440301 42600344 44134228 44200081 44209174 44931681 45869814 46041001 46585169 46588101 51142598 52795814 54261464 54918617 55618689 55780290 56191381 56431577 56798421 60115395 60807681 64215259 64304032 65309401 65318371 65749626 66222891 67105189 69615201 69616321 69900291 71858479 72210151 72344049 74283441 75399291 75803041 76246401 76724305 78112001 78733331 79850601 81017845 81071009 81714901 82939521 83229593 84036501 84831069 85161432 90959063 91765053 91908376 92823233 98914951 99028729
|
||||
728 371 40 971 672 561 234 644 680 991 772 752 12 678 80 622 555 992 450 940 188 484 87 195 605 604 36 707 260 96 168 0 817 860 155 76 584 890 980 61 109 541 145 336 771 640 504 760 150 104 475 567 455 813 721 398 416 311 760 567 30 352 904 500 404 66 432 474 872 320 936 375 184 780 548 136 48 232 726 360 760 720 248 588 594 500 640 734 572 486 772 684 863 784 352 486 19 74 50 760
|
5000
src/20241116/sales/sales4.ans
Normal file
5000
src/20241116/sales/sales4.ans
Normal file
File diff suppressed because it is too large
Load Diff
3
src/20241116/sales/sales4.in
Normal file
3
src/20241116/sales/sales4.in
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user