题解:洛谷 P6184 Building A Fence

【题目来源】

洛谷:P6184 [USACO08OCT] Building A Fence G - 洛谷

【题目描述】

勤奋的 Farmer John 想要建造一个由四面围成的栅栏来关住那些奶牛。他现在有一块长度为\(N\)\(4 \leq N \leq 2,500\))的长木板,他需要把这块长木板切成边长均为正整数的四块,使得他能建造一个栅栏。

请问他有多少种不同的切割方式能使切割出来的木板围成一个四面的栅栏。

注意:

  1. 不要考虑对称性的问题,不需要去除对称的方案和类似的复杂问题;
  2. 栅栏围成的面积必须大于 0;
  3. 结果可以用 32 位整数存储。

【输入】

一个整数 \(N\)

【输出】

Farmer John 能将木板分割开来并能围成四边形的方案数。

【输入样例】

6

【输出样例】

6

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的木板,需切成 \(4\) 段正整数长度的木板围成四边形。求不同的切割方案数。根据四边形不等式,任意三边之和大于第四边等价于最长边 \(< N/2\)(因为四边之和为 \(N\),最长边 \(< N - \text{最长边}\),即 \(\text{最长边} < N/2\))。本质是一个带约束的整数拆分计数问题。

  2. 算法选择

    • 线性动态规划:用 \(\text{dp}[i][j]\) 表示用 \(i\) 条边凑成长度 \(j\) 的方案数,通过三层循环递推求解
  3. 关键步骤

    • 初始化\(\text{dp}[0][0] = 1\)\(0\) 条边凑成长度 \(0\)\(1\) 种方案)
    • 状态转移\(i\)\(1\)\(4\)\(j\)\(1\)\(N\)):
      • 枚举第 \(i\) 条边的长度 \(k\)\(1\)\(\min(j, \lfloor(N-1)/2\rfloor)\)
      • 转移方程:\(\text{dp}[i][j] = \sum_{k=1}^{\min(j, \lfloor(N-1)/2\rfloor)} \text{dp}[i-1][j-k]\)
      • 解释:第 \(i\) 条边长度为 \(k\) 时,前 \(i-1\) 条边需凑成 \(j-k\),累加所有可能的 \(k\)
    • 约束体现\(k \leq \lfloor(N-1)/2\rfloor\) 确保每条边长度严格小于 \(N/2\),从而保证四边形不等式(最长边 \(<\) 其他三边之和)
    • 输出答案\(\text{dp}[4][N]\)\(4\) 条边恰好凑成总长度 \(N\) 的方案数)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(4 \cdot N \cdot N/2) = O(N^2)\),三层循环中 \(i \leq 4\)\(k \leq N/2\)
    • 空间复杂度:\(O(4 \cdot N) = O(N)\),二维 \(\text{dp}\) 数组
  5. 线性DP整数拆分的核心思想

    • 阶段划分:将"切成 \(4\) 段"分解为 \(4\) 个阶段,每个阶段添加一条边,逐步构建完整方案
    • 四边形约束转化:不直接判断"任意三边之和 \(>\) 第四边",而是利用总长度为 \(N\) 的特性,将约束转化为"每边 \(< N/2\)",大幅降低判断复杂度
    • 前缀和优化空间:当前代码为 \(O(N^2)\),可通过前缀和将内层求和优化到 \(O(1)\),整体降至 \(O(N)\),但 \(N \leq 2500\) 时当前复杂度已足够
    • 计数DP的标准框架\(\text{dp}[i][j] = \sum \text{dp}[i-1][j-k]\) 是经典的"将 \(j\) 拆分为 \(i\) 个正整数之和"的递推形式
    • 适用于整数拆分计数、带约束的组合方案、多阶段决策类问题

【解题思路】

【算法标签】

普及- #线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, dp[5][2505];
int main()
{
    cin >> n;  // 输入n
    dp[0][0] = 1;  // 递推式初始值为1
    for (int i=1; i<=4; i++) {  // 连接第i条边
        for (int j=1; j<=n; j++) {  // 第i条边的总长度j
            for (int k=1; k<=min(j, (n-1)/2); k++) {  // 增加一条边k,k的长度需要小于等于n的一半(向下取整)
                dp[i][j] = dp[i][j] + dp[i-1][j-k];  // 累加
            }
        }
    }
    cout << dp[4][n] << endl;
    return 0;
}

【运行结果】

6
6
posted @ 2026-08-29 09:39  团爸讲算法  阅读(10)  评论(0)    收藏  举报