1 条题解

  • 0
    @ 2025-12-10 10:54:31

    题目分析

    需要构造一个 n+1×n+1n+1 \times n+10/10/1 矩阵 AA,满足以下条件:

    1. 主对角线 Ai,i=1A_{i,i} = 1
    2. 次对角线 Ai,i+1=1A_{i,i+1} = 1
    3. 下三角部分 Ai,j=0A_{i,j} = 0(当 i>ji > j
    4. 闭包性:如果 Ai,j=1A_{i,j} = 1ji>1j-i > 1,则存在 i<t<ji < t < j 使得 Ai,t=At,j=1A_{i,t} = A_{t,j} = 1
    5. 幂正性(Ak)i,j>0(A^k)_{i,j} > 0 对所有 iji \leq j 成立

    要求输出所有满足 Ai,j=1A_{i,j} = 1ji>1j-i > 1(i,j)(i,j) 对,使得 mm(这样的对数)尽可能小。

    核心思路

    1. 问题转化

    这个问题实际上是在构造一个有向图

    • 节点 0,1,,n0,1,\dots,n
    • iji \to j 表示 Ai,j=1A_{i,j} = 1
    • 条件4意味着图是传递闭包的:如果存在长路径,必须有短路径作为"桥梁"
    • 条件5要求从 iijj 存在长度不超过 kk 的路径

    2. 算法框架

    代码实现的是递归构造算法

    // 关键数据结构
    ll f[K][N], g[K][N];               // DP数组
    pair<int, int> frf[K][N];          // 决策记录
    int frg[K][N];                     // 分组决策记录
    

    算法实现详解

    1. DP预处理

    定义 f[k][n]f[k][n]:对于 nn 个节点,在约束 kk 下需要的最小边数(不包括主对角线和相邻边)。

    状态转移:

    1. 基础情况

      for (int i = 0; i <= k; i++) f[i][0] = f[i][1] = 0;
      for (int j = 2; j <= n; j++) f[1][j] = j * (j - 1) / 2;  // 完全图
      
    2. 分组优化: 将节点分组,组内用 f[k]f[k] 处理,组间用 f[k2]f[k-2] 处理:

      // g[k][j]:将 j 个节点分成 t+1 个组的最小边数
      ll val = f[i - 2][t + 1] + (j - t - 1) * 2 + 
               ((j - 1) % t) * f[i][(j - 1) / t] + 
               (t - (j - 1) % t) * f[i][(j - 1) / t - 1];
      
    3. 对称分割: 将 nn 个节点分成三段:左、中、右,分别处理:

      // 分割点:qa 左段长度,qb 右段长度
      for (int t = 0; t * 2 < j; t++) {
          ll val = g[i][j - t * 2] + f[i][t] * 2 + t * 2;
          if (val < f[i][j]) f[i][j] = val, frf[i][j] = {t, t};
      }
      

    2. 构造算法

    主函数 dof(vec, k)

    处理节点集合 vec,约束为 kk

    void dof(basic_string<int> vec, int k) {
        if (vec.size() <= 1) return;
        if (k == 1) {
            // 完全图:所有点对间连边
            for (int u : vec)
                for (int v : vec)
                    if (u < v) addedge(u, v);
            return;
        }
        
        // 获取最优分割方案
        int qa = frf[k][siz].first, qb = frf[k][siz].second;
        
        if (!qa && !qb) return dog(vec, k);  // 直接分组处理
        
        // 分成三段:左段、中段、右段
        basic_string<int> ta, tb, tc;
        
        // 左段向中间段的第一个节点连边
        for (int i = 0; i < qa; i++) 
            ta += vec[i], addedge(vec[i], vec[qa]);
        
        // 中间段
        for (int i = qa; i < vec.size() - qb; i++) 
            tc += vec[i];
        
        // 右段从中间段的最后一个节点接收边
        for (int i = vec.size() - qb; i < vec.size(); i++) 
            tb += vec[i], addedge(vec[vec.size() - qb - 1], vec[i]);
        
        // 递归处理各段
        dof(ta, k), dof(tb, k);
        dog(tc, k);  // 中间段用分组方法处理
    }
    

    分组函数 dog(vec, k)

    将节点分组,组内处理,组间用 k2k-2 处理。

    void dog(basic_string<int> vec, int k) {
        int t = frg[k][vec.size()];  // 最优分组数
        
        // 创建组:每组包含若干节点
        basic_string<int> pvp;  // 每组代表节点
        pvp += vec[0];  // 第一组的代表
        
        int qaq = 0;
        for (int i = 0; i < t; i++) {
            // 构建第i组
            basic_string<int> qwq;
            int group_size = ...;  // 根据公式计算
            
            // 组内完全连接
            for (int node : qwq) {
                addedge(vec[qaq], node);      // 代表到组员
                addedge(node, vec[qaq + group_size]);  // 组员到下一组代表
            }
            
            dof(qwq, k);      // 递归处理组内
            qaq += group_size;
            pvp += vec[qaq];  // 下一组代表
        }
        
        dof(pvp, k - 2);  // 组间用 k-2 处理
    }
    

    关键算法思想

    1. 分治递归

    • 将大问题分解为小问题
    • 通过递归处理各子段
    • 边界情况 k=1k=1 退化为完全图

    2. 分组优化

    • 将节点分成若干组
    • 组内用当前的 kk 约束处理
    • 组间用 k2k-2 约束处理(因为跨越了两个组)

    3. 三段分割

    • 左段:只向右段第一个节点连边
    • 中段:分组处理
    • 右段:从左段最后一个节点接收边
    • 保证任意两点间存在长度 ≤ kk 的路径

    复杂度分析

    时间复杂度

    • DP预处理O(kn2)O(k \cdot n^2)n2000n \leq 2000k15k \leq 15
    • 构造阶段O(m)O(m)mm 为输出边数
    • 总复杂度可接受

    空间复杂度

    • DP数组:O(kn)O(k \cdot n)
    • 构造过程中的临时数组:O(n)O(n)

    算法标签

    主要算法

    • 动态规划
    • 递归构造
    • 分治策略

    优化技术

    • 分组优化
    • 记忆化搜索
    • 决策记录

    问题特征

    • 图构造
    • 闭包性质
    • 路径长度约束

    总结

    这道题通过巧妙的DP设计和递归构造,实现了在 kk 步可达性约束下的最小边数图构造。算法核心在于将问题分解为子问题,通过分组和分段策略,平衡不同部分间的连接需求,最终以接近最优的边数满足所有约束条件。

    • 1

    「北大集训 2021」末日魔法少女计划

    信息

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