题解:洛谷 P9011 Air Cownditioning II

【题目来源】

洛谷:P9011 [USACO23JAN] Air Cownditioning II B - 洛谷

【题目描述】

农夫约翰的 \(N\) 头奶牛 \((1\le N\le 20)\) 住在一个谷仓里,谷仓里有连续的牛栏,编号为 $1-100 $。奶牛 \(i\) 占据了编号\([s_i,t_i]\) 的牛栏。不同奶牛占据的牛栏范围是互不相交的。奶牛有不同的冷却要求,奶牛\(i\)占用的每个牛栏的温度必须至少降低 \(c_i\) 单位。

谷仓包含 \(M\) 台空调,标记为 \(1-M(1\le M\le 10)\)。第\(i\)台空调需要花费 \(m_i\) 单位的金钱来运行 \((1\le m_i\le 1000)\),如果运行,第 \(i\) 台空调将牛栏\([a_i,b_i]\) 所有牛栏的温度降低 \(p_i(1\le p_i\le 10^6)\)。空调覆盖的牛栏范围可能会重叠。

请帮助农夫约翰求出满足所有奶牛需求要花费的最少金钱。

【输入】

第一行两个整数,分别为 \(N\)\(M\)

\(2\)\((N+1)\) 行,每行三个整数,分别为\(s_i\)、$t_i $和 \(c_i\)

\((N+2)\)\((M+N+1)\) 行,每行四个整数,分别为 \(a_i\)\(b_i\)\(p_i\)\(m_i\)

【输出】

一个整数,表示最少花费的金钱。

【输入样例】

2 4
1 5 2
7 9 3
2 9 2 3
1 6 2 8
1 2 4 2
6 9 1 5

【输出样例】

10

【核心思想】

  1. 问题分析:谷仓有100个牛栏,\(N\) 头奶牛(\(N \le 20\))占据互不相交的连续区间 \([s_i, t_i]\),每头奶牛对其区间内每个牛栏有降温需求 \(c_i\)。有 \(M\) 台空调(\(M \le 10\)),每台空调覆盖区间 \([a_i, b_i]\),降温能力 \(p_i\),运行费用 \(m_i\)。求选择若干台空调运行,使得每个牛栏的实际降温总量 \(\ge\) 该处需求,且总费用最小。本质是集合覆盖问题的变种——用最少的费用覆盖长度仅为100的离散数轴上的需求点,由于 \(M\) 极小(\(\le 10\)),直接枚举所有子集即可。

  2. 算法选择

    • DFS 枚举子集:对 \(M\) 台空调每台选或不选,共 \(2^M \le 1024\) 种方案,完全可行
    • 可行性检查:对每种方案,累加选中空调在各牛栏的降温量,与需求数组逐位比较
    • 剪枝优化:可在DFS过程中维护当前费用,若已超过当前最优解则提前回溯(示例代码未实现,但可作为优化)
  3. 关键步骤

    • 需求预处理:读入 \(N\) 头奶牛的信息 \((s_i, t_i, c_i)\),对牛栏 \(j \in [s_i, t_i]\),令 \(need[j] = c_i\)(由于奶牛区间互不相交,每个位置只被一头奶牛覆盖)
    • DFS 搜索(从第1台到第 \(M\) 台):
      • 当前考虑第 \(step\) 台空调,若 \(step > M\),说明已形成一种完整选择方案
      • 调用检查函数 \(check()\):清空实际降温数组 \(cool[1..100]\),遍历所有空调,若被选中则将 \(p_i\) 累加到 \([a_i, b_i]\) 区间;遍历 \(1\)\(100\) 号牛栏,若存在 \(cool[j] < need[j]\) 则方案非法,否则返回总费用
      • 若方案合法,更新全局最小费用 \(ans = \min(ans, cost)\)
      • 回溯时分别递归“不选第 \(step\) 台”和“选第 \(step\) 台”两个分支
    • 输出答案\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(2^M \cdot (M \cdot L + L))\),其中 \(L = 100\) 为牛栏总数,\(M \le 10\),方案数 \(\le 1024\),实际计算量极小
    • 空间复杂度:\(O(L + M)\),存储需求数组、降温数组和空调信息
  5. DFS 枚举子集的核心思想

    • 小数据规模驱动\(M \le 10\) 是本题的关键约束,使得指数级枚举完全可行,无需复杂的 DP 或贪心
    • 离散化区间处理:牛栏编号固定为 \(1 \sim 100\),可直接用数组模拟差分或逐点累加,避免区间合并的复杂逻辑
    • 可行性检查与费用分离:先独立计算当前方案能否满足全部需求,再统计费用,逻辑清晰
    • 适用场景:当可选物品数量 \(\le 20\) 时,DFS 枚举所有子集是解决“最少费用满足覆盖需求”类问题的首选思路,配合必要的剪枝可应对 \(M\) 稍大的情况

【解题思路】

【算法标签】

普及- #DFS-一维

【代码详解】

// 这个老师的DFS模板会超时,需要选择学而思提供的DFS模板
#include <bits/stdc++.h>
using namespace std;
int n, M;
int fence[105], fence2[105]={0};  // 定义fence记录奶牛需要的每个牛栏的温度,定义fence2记录每台空调开启后每个牛栏的温度
struct air {
    int a, b, p, m;
}a[15];
int book[15] = {0}, ans=1e9;
void dfs(int step)
{
    memset(fence2, 0, sizeof(fence2));  // 初始化fence2数组
    int sum = 0;  // 定义sum,记录开启空调后的花费,每次初始化为0
    for (int i=1; i<=M; i++) {  // 遍历M台空调
        if (book[i]==1) {  // 对于选中的那些空调
            for (int j=a[i].a; j<=a[i].b; j++) {  // 将每台空调对应的牛栏温度进行累加
                fence2[j]+=a[i].p;
            }
            sum += a[i].m;  // 并将这些空调运行所需的金钱相加
        }
    }
    int tot=0;  // 定义tot统计多少个牛栏已经满足温度降低(这里是提升)要求
    for (int i=1; i<=100; i++) {  // 遍历所有牛栏
        if (fence2[i]<fence[i]) break;  // dfs剪枝操作,如果某个位置牛栏上升温度低于要求,直接退出
        else tot++;  // 否则对这个牛栏进行计数
    }
    if (tot==100) {  // 如果满足要求,应该100个牛栏都满足要求
        ans = min(ans, sum);  // 此时计算花费最少的金钱
        return;  // 退出这轮搜索
    }
    for (int i=1; i<=M; i++) {  // dfs的深搜过程,遍历M个空调
        if (book[i]==0) {  // 对于没有被选中的空调
            book[i] = 1;  // 选中它
            dfs(step+1);  // 进行下一步计算
            book[i] = 0;  // 退回时还原现场
        }
    }
}
int main()
{
    cin >> n >> M;  // 输入n和M(因为M与后面的m同名,所以这里用大写)
    for (int i=1; i<=n; i++) {  // 遍历n行
        int s, t, c;
        cin >> s >> t >> c;  // 输入s、t和c
        for (int j=s; j<=t; j++) {  // 在数轴上将每个位置的值修改为c
            fence[j] = c;
        }
    }
    for (int i=1; i<=M; i++) {  // 遍历m行
        cin >> a[i].a >> a[i].b >> a[i].p >> a[i].m;  // 输入每台空调的a、b、p和m
    }
    dfs(1);  // 开始dfs搜索(这里是0还是1开始没有关系,因为定义了book列表来记录某台空调是否被选中)
    cout << ans << endl;

    return 0;
}
// 学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int n, M;
int f[105], h[105];
// f[i]表示i号牛栏需要降低的温度,h[i]表示i号牛栏实际降低的温度
int a[15], b[15], p[15], m[15];  // 第i台空调的降温范围[ai,bi],降低温度pi,话费mi;
bool used[15];  // used[i]表示标记是否选择第i台空调
int ans = 2e9;
int chk()
{
    memset(h, 0, sizeof(h));
    int cost = 0;
    for (int i=1; i<=M; i++) {  // 枚举第i台空调
        if (used[i]==true) {  // 若当前方案选择了第i台空调
            for (int j=a[i]; j<=b[i]; j++) {  // 枚举第i台空调的影响牛栏,进行降温
                h[j] += p[i];  // j号牛栏温度降低p[i]
            }
            cost += m[i];  // cost加上第i台空调的花费
        }
    }
    for (int i=1; i<=100; i++) {
        if (h[i]<f[i]) return -1;
    }
    return cost;
}
void dfs(int step)  // 接下来考虑是否选择第step台空调
{
    if (step==M+1) {
        // 若接下来要搜索第M+1台空调,表示前面M台空调全部搜完了,得到了一种空调选择方案
        int cost = chk();  // 检查当前空调选择方案是否能满足所有奶牛的降温需求,并求出总花费
        if (cost != -1) {
            ans = min(ans, cost);  // 对于合法方案的花费,取最小值
        }
        return;
    }
    //不选第step台空调
    used[step]=false;
    dfs(step+1);
    used[step]=false;
    //选第step台空调
    used[step]=true;
    dfs(step+1);
    used[step]=false;
}
int main()
{
    cin >> n >> M;
    for (int i=1; i<=n; i++) {
        int s, t, c;
        cin >> s >> t >> c;  // 第i头奶牛的牛栏范围[s,t],降温需求c
        for (int j=s; j<=t; j++) {  // 枚举j号牛栏
            f[j] = c;
        }
    }
    for (int i=1; i<=M; i++) {
        cin >> a[i] >> b[i] >> p[i] >> m[i];
    }
    dfs(1);  // 从第1台空调开始搜索所有空调选择方案
    cout << ans << endl;
    return 0;
}

【运行结果】

2 4
1 5 2
7 9 3
2 9 2 3
1 6 2 8
1 2 4 2
6 9 1 5
10
posted @ 2026-08-29 09:41  团爸讲算法  阅读(7)  评论(0)    收藏  举报