一道由数学作业延伸出的构造题
先放原题:
将正整数划分为组,使得每组内任意两数之和不等于任何完全平方数。求的最小值。(《培优新方法》八年级数学 §23 第3题)
嗯,对手搓暴力枚举来说数据确实大了点。抱着“反正也不可能求出正解”的心态,我尝试遍历了这些数,在第遍历时,将符合要求的数依次加入第组。具体来讲,如果数和第组里的任何数之和都不是完全平方数,则将加入该组并在随后的遍历中忽略。最终一共遍历了遍才将所有数分完,所以我大剌剌填了上去。往后一翻——答案就是。
然后某人发表暴论:“这有点像一道编程题。我在一本讲程序设计的书上看到过。”我同意。于是就有了这篇水帖。
现在问题变成了:
输入正整数,将集合划分为个子集,有,且有。输出最小的
及具体划分方案(所以别想打表)。
首先我们证明上面贪心算法的正确性。令第趟遍历构成集合。为了保证符合要求,即,对于扫描到的任意两个冲突的数,它们一定不能处于同一集合中,也即至少需要一个新集合来处理冲突的数,不会使最终结果变差。而我们将任意两个不冲突的数都尽可能放入同一集合中(应收尽收),综上这能够保证最小。
我们可以预处理出不超过的完全平方数。由于我们采用顺序遍历,易知扫描到数时,只需检查完全平方数,查询数是否在中即可。用简化版的并查集实现集合的查询和合并。
哦,你说时间复杂度?我没能准确地算出来。根据之前尝试的结果,数据在以内时,时间都还可以接受。但若假设每个数平均会在遍历遍之后被划分到一个集合中;易知每次检查数,都将扫描约个数,共花费的时间;二分查找花费级别的时间,故最终大约花费的时间。
这应该不是最高效的算法。(是,我明白,这的确挺暴力的,但我也想不出其他办法。)欢迎从数轮角度分析并提出更快的解法。
我也想过二分的值(很明显答案具有单调性),但不妙的是,至今我仍未想出check ()
函数怎么写。如何高效地判定在分组时能否满足要求呢?
或者,有无可能……直接推导出某一数学结论呢?
总之,上述解法的代码实现如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
#include <bits/stdc++.h> using namespace std; const int N = 1024000; int n, ans, x, cnt, q; int fa[N], nums[N]; bool solved[N]; void solve (int g) { int p = upper_bound (nums + 1, nums + 1 + q, g) - nums; // 最小的大于g的完全平方数 for (int i = p; i <= q && nums[i] < g<<1; ++i) if (fa[nums[i] - g] == fa[x]) // 分组冲突 return; fa[g] = fa[x]; // 合并 solved[g] = 1; ++cnt; } int main () { scanf ("%d", &n); for (int i = 1; i <= n; ++i) fa[i] = i; // 初始化并查集 for (int i = 1; i * i <= n<<1; ++i) nums[++q] = i * i; // 预处理完全平方数 while (cnt < n) { ++ans, x = 1; // 新一轮遍历 while (solved[x]) ++x; for (int i = x; i <= n; ++i) if (!solved[i]) solve (i); } printf ("%d", ans); // 我懒,所以就不去重并输出具体方案了(雾 return 0; }