1 条题解

  • 0
    @ 2026-10-26 1:48:52

    思路与算法

    先筛出1..n每个数的真约数和sum[x]:枚举约数d,将它累加给2d、3d等倍数,因而不会把x自身计入sum[x]。

    仅当1≤sum[x]<x时,连接x和sum[x]。若把较小者视为父亲,每个点最多有一个父亲,且父节点编号严格减小,因此得到若干棵树。任意不重复的变换序列就是森林中的一条简单路径,答案是各树直径的最大边数。

    不必显式存储邻接表。按编号从n递减到1处理,子节点必已处理完;维护每点最长、次长的两条向下分支。经过x的最长路径长度为first[x]+second[x],用其更新答案。若x有合法父亲,将first[x]+1作为父亲的一条新分支,更新父亲的前两大值。

    正确性说明

    筛法恰好把每个严格小于x的正约数加给x,故真约数和正确。每条转换边都由较大的端点指向唯一较小的父亲;若存在无向环,环上最大编号点必须有两个更小的相邻点,与其只有一个父亲矛盾,所以图是森林。

    树中任何简单路径都在某个最高点处分成至多两条不同孩子分支。递减处理时该点的全部孩子已将各自最长分支传来,前两大分支之和就是以该点为最高点的最长路径。枚举所有最高点以及所有树,得到全森林的最长简单路径,其边数即最多转换步数。

    复杂度

    约数和数组及两条分支数组初始化O(n)。筛法访问总次数为O(n log(n+1)),递减聚合O(n)。总时间O(n log(n+1)),空间O(n)。无需邻接表或递归栈。

    易错点

    1的真约数和为0,但0不在允许的正整数范围内,不能连到0。完全数的约数和等于自身,也不能加自环;约数和大于自身时同样不连边。答案统计边数而非点数,n=1应输出0。图可能有多棵树,不能只计算包含1的连通分量。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n;
        cin >> n;
        vector<int> sum(n + 1), first(n + 1), second(n + 1);
        for (int d = 1; d <= n / 2; ++d)
            for (int x = d * 2; x <= n; x += d)
                sum[x] += d;
        int answer = 0;
        for (int x = n; x >= 1; --x) {
            answer = max(answer, first[x] + second[x]);
            int p = sum[x];
            if (p > 0 && p < x) {
                int length = first[x] + 1;
                if (length > first[p]) {
                    second[p] = first[p];
                    first[p] = length;
                } else if (length > second[p])
                    second[p] = length;
            }
        }
        cout << answer << '\n';
    }
    
    • 1

    信息

    ID
    1640
    时间
    1000ms
    内存
    512MiB
    难度
    普及
    标签
    递交数
    1
    已通过
    1
    上传者