题解:学而思编程 兔子的工作
【题目来源】
学而思编程:兔子的工作
【题目描述】
兔子先生大学毕业后找到了一份实习工作,如果实习通过它就能转正了。
实习期共有 \(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
【核心思想】
-
问题分析:给定 \(N\) 天实习期,每天可选择工作或休息(部分天强制休息),连续工作第 \(k\) 天消耗体力 \(k\)。求在总体力消耗不超过 \(K\) 的前提下,最多能工作多少天。这是一个三维动态规划问题,需要记录连续工作长度来计算体力消耗。
-
算法选择:
- 三维 DP:\(dp[i][j][k]\) 表示前 \(i\) 天,工作了 \(j\) 天,最后一段连续工作长度为 \(k\) 时的最小体力消耗
- 状态设计:第三维 \(k\) 用于计算连续工作的体力消耗(第 \(k\) 天消耗 \(k\) 点体力)
-
关键步骤:
- 初始化:\(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N^3)\),三重循环(天数、工作天数、连续长度)
- 空间复杂度:\(O(N^3)\),三维 DP 数组,可优化至 \(O(N^2)\)
-
三维 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
浙公网安备 33010602011771号