1 条题解

  • 0
    @ 2025-10-17 10:44:31

    题目深度分析

    问题本质

    我们有一个 n 个节点的完全图,但每条边被定向了,形成了一个竞赛图 (tournament graph)

    竞赛图的性质:

    • 任意两个不同节点之间恰好有一条有向边
    • 可能存在环,但整体结构有很好的性质

    关键定理

    竞赛图结构定理:任何竞赛图都可以唯一分解为强连通分量 S1,S2,,SkS_1, S_2, \ldots, S_k,满足:

    • 每个 SiS_i 是强连通的
    • 所有 SiS_iSjS_j (i<ji < j) 的边都是从 SiS_i 指向 SjS_j

    这意味着 SCC 形成一个全序链

    路径长度分析

    从节点 uu 出发的最长简单路径:

    • 如果 uSiu \in S_i,那么它能到达 Si,Si+1,,SkS_i, S_{i+1}, \ldots, S_k 中的所有节点
    • 不能到达 S1,,Si1S_1, \ldots, S_{i-1}(因为边方向限制)

    所以最长路径长度 = j=ikSj\sum_{j=i}^k |S_j|

    路径构造方法

    对于每个强连通分量 SiS_i

    • 强连通竞赛图一定有哈密顿圈(经典结论)
    • 我们可以构造哈密顿路径

    构造算法(增量法):

    1. 从单个节点开始
    2. 每次加入一个新节点 xx
      • 如果 xx 指向路径头,插入开头
      • 如果路径尾指向 xx,插入末尾
      • 否则,找到位置 pp 使得 path[p]xpath[p] \to xxpath[p+1]x \to path[p+1],插入中间

    算法步骤

    1. 读入并建图O(n2)O(n^2)
    2. 求 SCC:用 Kosaraju 或 Tarjan,O(n2)O(n^2)
    3. 拓扑排序 SCC:竞赛图中 SCC 自然有序
    4. 计算后缀和:每个 SCC 开始的最长路径长度
    5. 为每个起点构造路径O(n2)O(n^2)

    核心算法解释

    // SCC分解和拓扑排序
    void kosaraju() {
        // 第一次DFS确定完成时间
        for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i);
        
        // 第二次DFS在反图上找SCC
        for(int i=n-1;i>=0;i--) {
            int u=order[i];
            if(!vis[u]) dfs2(u,compCount++);
        }
        
        // 拓扑排序SCC:通过比较边方向
        sort(SCC,SCC+compCount,[&](int a,int b){
            return g[SCC[a][0]][SCC[b][0]];
        });
    }
    
    // 构造哈密顿路径
    vector<int> buildPath(vector<int>& nodes) {
        vector<int> path={nodes[0]};
        for(int i=1;i<nodes.size();i++) {
            int x=nodes[i];
            if(g[x][path[0]]) path.insert(path.begin(),x);
            else if(g[path.back()][x]) path.push_back(x);
            else {
                for(int j=0;j+1<path.size();j++)
                    if(g[path[j]][x]&&g[x][path[j+1]]){
                        path.insert(path.begin()+j+1,x);
                        break;
                    }
            }
        }
        return path;
    }
    
    // 主逻辑
    int main() {
        // 读入建图
        cin>>n;
        for(int i=2;i<=n;i++)
            for(int j=1;j<i;j++)
                cin>>g[j][i];  // 1:j->i, 0:i->j
        
        kosaraju();
        
        // 计算每个SCC开始能访问的节点总数
        vector<int> suffix(compCount+1,0);
        for(int i=compCount-1;i>=0;i--)
            suffix[i]=suffix[i+1]+SCC[i].size();
        
        // 为每个起点输出最长路径
        for(int start=1;start<=n;start++) {
            int cid=comp[start];
            vector<int> path;
            for(int i=cid;i<compCount;i++) {
                auto seg=buildPath(SCC[i]);
                path.insert(path.end(),seg.begin(),seg.end());
            }
            cout<<suffix[cid];
            for(int u:path) cout<<" "<<u;
            cout<<"\n";
        }
    }
    

    复杂度分析

    • 建图O(n2)O(n^2),必须读取所有输入
    • KosarajuO(n2)O(n^2),虽然理论 O(n+m)O(n+m)m=n(n1)/2m = n(n-1)/2
    • 构造路径O(n2)O(n^2),每个节点插入需要线性时间
    • 总复杂度O(n2)O(n^2),对于 n2000n \leq 2000 可行

    样例验证

    以题目样例为例:

    4
    1
    1 1  
    1 0 1
    

    建图:

    1→2, 1→3, 1→4
    2→3, 4→2  
    3→4
    

    SCC 分解:{1}, {2,3,4}

    • 从 1 出发:可以访问所有 4 个城市
    • 从 2,3,4 出发:只能访问 {2,3,4} 中的 3 个城市

    输出:

    4 1 2 3 4
    3 2 3 4  
    3 3 4 2
    3 4 2 3
    

    完全符合题目要求。


    总结

    这道题的核心在于理解竞赛图的结构性质:

    1. SCC 链式排序
    2. 强连通竞赛图有哈密顿圈
    3. 从某点出发的最长路径由它所在的 SCC 位置决定

    利用这些性质,我们就能高效解决问题。

    • 1

    信息

    ID
    3206
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    37
    已通过
    1
    上传者