1 条题解
-
0
题意简述
有 个顶点,第 个顶点有权值 。依次进行 次操作:每次操作选择两个尚未被连接(任意状态都可以,只要不形成自环且满足条件)的顶点 ,要求 能被 整除,然后将它们连边。问能否通过这 次操作构造出一棵生成树(连通图),若能则输出方案。
解法
存在性:总是存在方案,因为可以在每一步都找到合法的边加入,最终形成一棵树。关键在于算法的构造。
从大到小考虑 (即 )。对于当前 ,我们需要找出一对未被加入的顶点 使得 。这等价于 和 模 同余。利用鸽巢原理,当前未使用的顶点集合大小为 (因为已经加入了 条边,未使用顶点数从 递减到 )。将这 个顶点根据模 的余数放入 个桶中,由鸽巢原理,必有一个桶包含至少两个顶点,这两个顶点模 同余,即它们的差被 整除。我们可以选择这两个顶点连边。然后将其中一个从未使用集合中移除(因为它已经连入生成树中,另一个保留在集合中继续使用)。
因此,算法为:
- 维护一个“活跃”顶点集合(初始包含所有顶点)。从 到 逆序处理:
- 建立 个桶,将活跃顶点按其 放入对应桶。
- 找到第一个包含至少两个顶点的桶,从中取出两个顶点 和 ,输出边 。
- 从活跃集合中删去 (或 之一,保留另一个)。
由于每次都能找到合法边且步骤数正好 ,最终必然形成连通图(实际上是一棵树,因为每次操作连接两个不同集合,且顶点数逐步减少)。
时间复杂度:每次操作需要遍历活跃集合并建立桶,单次 ,总复杂度 。由于 , 完全可行。
参考代码
#include <bits/stdc++.h> using namespace std; int main() { int tests; cin >> tests; while (tests--) { int n; cin >> n; vector<int> a(n); for (auto& i : a) cin >> i; vector<int> pos(n); iota(pos.begin(), pos.end(), 0); vector<pair<int, int>> ans; for (int i = n - 1; i; --i) { vector<int> occ(i, -1); for (auto j : pos) { if (occ[a[j] % i] != -1) { ans.emplace_back(j, occ[a[j] % i]); pos.erase(find(pos.begin(), pos.end(), j)); break; } occ[a[j] % i] = j; } } reverse(ans.begin(), ans.end()); cout << "YES\n"; for (auto [x, y] : ans) cout << x + 1 << ' ' << y + 1 << '\n'; } } - 维护一个“活跃”顶点集合(初始包含所有顶点)。从 到 逆序处理:
- 1
信息
- ID
- 6919
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者