题解:学而思编程 兔子的工作

【题目来源】

学而思编程:兔子的工作

【题目描述】

兔子先生大学毕业后找到了一份实习工作,如果实习通过它就能转正了。

实习期共有 \(N\) 天,其中有几天公司集体放假,兔子先生不用上班,剩下时间它可以选择工作或者休息。

兔子先生工作总是越来越累,可是每当它休息时,它就重新充满了能量。简而言之,兔子先生第一天工作时这一天会消耗体力 \(1\),连续第二天工作时这一天会消耗体力 \(2\),连续第三天工作时这一天会消耗体力 \(3\),以此类推......

每当兔子先生休息后,工作的第一天又会消耗体力 \(1\)

为了让boss满意,兔子先生想工作尽量多的天数,但是懒惰的兔子先生又想让自己的总体力消耗不超过 \(K\)

【输入】

第一行两个整数 \(N(1≤N≤400)\)\(K(1≤K≤\frac{N×(N+1)}{2}\),表示一共有 \(N\) 天,以及总体力不超过 \(K\)

第二行是一个长度为 \(N\)\(01\) 字符串。如果第 \(i\) 个字符为 \(1\),表示第 \(i\) 天兔子先生可以选择工作或者休息,如果第 \(i\) 个字符为 \(0\),则表示公司这天集体放假,兔子先生休息。

【输出】

输出一行,包含一个整数,表示兔子先生最多能工作的天数。

【输入样例】

4 2
1011

【输出样例】

2

【核心思想】

  1. 问题分析:给定 \(N\) 天实习期,每天可选择工作或休息(部分天强制休息),连续工作第 \(k\) 天消耗体力 \(k\)。求在总体力消耗不超过 \(K\) 的前提下,最多能工作多少天。这是一个三维动态规划问题,需要记录连续工作长度来计算体力消耗。

  2. 算法选择

    • 三维 DP\(dp[i][j][k]\) 表示前 \(i\) 天,工作了 \(j\) 天,最后一段连续工作长度为 \(k\) 时的最小体力消耗
    • 状态设计:第三维 \(k\) 用于计算连续工作的体力消耗(第 \(k\) 天消耗 \(k\) 点体力)
  3. 关键步骤

    • 初始化\(dp[0][0][0] = 0\),其他状态初始化为无穷大
    • 状态转移
      • 不选当前天(休息):\(dp[i][j][0] = \min(dp[i][j][0], dp[i-1][j][k])\),连续长度重置为 \(0\)
      • 选当前天(工作,仅当 \(a[i] = '1'\)):\(dp[i][j][k] = \min(dp[i][j][k], dp[i-1][j-1][k-1] + k)\),连续长度增加 \(1\),体力消耗增加 \(k\)
    • 答案:从大到小枚举工作天数 \(j\),找到满足 \(dp[N][j][k] \leq K\) 的最大 \(j\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^3)\),三重循环(天数、工作天数、连续长度)
    • 空间复杂度:\(O(N^3)\),三维 DP 数组,可优化至 \(O(N^2)\)
  5. 三维 DP 的核心思想

    • 状态设计:增加第三维记录连续工作长度,用于准确计算体力消耗
    • 状态转移:休息则重置连续长度,工作则连续长度加 \(1\)
    • 最优子结构:当前最优解由子问题的最优解组合而成
    • 空间优化:由于只依赖前一天的状态,可以用滚动数组优化空间
    • 适用于有连续段代价的序列决策问题

【算法标签】

线性DP-一维

【代码详解】

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 405;
int n, t, dp[N][N][N];  // dp[i][j][k]: 前i个字符,选择了j个'1',最后一段连续长度为k的最小操作次数
string a;

int main()
{
    cin >> n >> t >> a;
    a = ' ' + a;  // 使字符串下标从1开始
    memset(dp, 0x3f, sizeof(dp));  // 初始化为无穷大
    dp[0][0][0] = 0;  // 初始状态

    for (int i = 1; i <= n; i++)
    {
        dp[i][0][0] = 0;  // 不选任何'1'的情况
        for (int j = 1; j <= i; j++)  // 选择的'1'的数量
        {
            // 不选当前字符
            for (int k = 0; k <= j; k++)
            {
                dp[i][j][0] = min(dp[i][j][0], dp[i - 1][j][k]);
            }

            // 如果当前字符是'1',选择它
            if (a[i] == '1')
            {
                for (int k = 1; k <= j; k++)
                {
                    // 选择当前字符,连续长度增加1
                    dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j - 1][k - 1] + k);
                }
            }
        }
    }

    // 从后向前找到满足条件的最多'1'数量
    for (int j = n; j >= 0; j--)
    {
        for (int k = 0; k <= j; k++)
        {
            if (dp[n][j][k] <= t)  // 操作次数不超过限制
            {
                cout << j;
                return 0;
            }
        }
    }
    return 0;
}

【运行结果】

4 2
1011
2
posted @ 2026-06-14 10:45  团爸讲算法  阅读(7)  评论(0)    收藏  举报