题目描述
你正在讲授一门课程,需要覆盖 n n n 个主题( 1 ≤ n ≤ 1000 1 \le n \le 1000 1≤n≤1000)。每节课时长为 L L L 分钟( 1 ≤ L ≤ 500 1 \le L \le 500 1≤L≤500),每个主题需要 t i t_i ti 分钟( 1 ≤ t i ≤ L 1 \le t_i \le L 1≤ti≤L)。对于每个主题,你必须决定在哪一节课中讲授。有两个限制:
- 每个主题必须完整地在一节课内讲授,不能分割到两节课中。
- 主题必须按顺序讲授,即主题 i i i 必须在主题 i + 1 i+1 i+1 之前讲授。
在这些限制下,有时一节课结束时会有剩余时间(空闲时间)。如果剩余时间不超过 10 10 10 分钟,学生乐于提前下课;如果剩余时间超过 10 10 10 分钟,学生则会感到不满意。定义一节课的不满指数( DI \texttt{DI} DI)如下:
设某节课实际使用时间为 m m m(即该节课中所有主题时间之和),则空闲时间 t = L − m t = L - m t=L−m。不满指数为:
DI ( t ) = { 0 , t = 0 , − C , 1 ≤ t ≤ 10 , ( t − 10 ) 2 , t > 10 , \texttt{DI}(t)= \begin{cases} 0, & t = 0,\\ -C, & 1 \le t \le 10,\\ (t-10)^2, & t > 10, \end{cases} DI(t)=⎩ ⎨ ⎧0,−C,(t−10)2,t=0,1≤t≤10,t>10,
其中 C C C 是给定的正整数。总不满指数为所有课程不满指数之和。
你的任务是:在满足上述约束的前提下,首先最小化使用的课程总数,然后在课程总数最小的方案中,最小化总不满指数。
输入格式
输入包含多组测试数据。每组数据第一行为整数 n n n,若 n = 0 n=0 n=0 则结束。第二行为两个整数 L L L 和 C C C。接下来一行包含 n n n 个整数 t 1 , t 2 , … , t n t_1, t_2, \ldots, t_n t1,t2,…,tn。
输出格式
对于每组数据,输出三行:
Case X:
Minimum number of lectures: Y
Total dissatisfaction index: Z
其中 X X X 为从 1 1 1 开始编号的案例号, Y Y Y 为最小课程数, Z Z Z 为对应的最小总不满指数。每组数据之间输出一个空行。
样例
输入
6
30 15
10 10 10 10 10 10
10
120 10
80 80 10 50 30 20 40 30 120 100
0
输出
Case 1:
Minimum number of lectures: 2
Total dissatisfaction index: 0
Case 2:
Minimum number of lectures: 6
Total dissatisfaction index: 2700
题目分析
本题有两个优化目标,且优先级不同:首要目标是最小化课程数量,次要目标是在课程数量最小时最小化总不满指数。
由于主题必须按顺序连续分配(不能打乱顺序),并且每个主题不可分割,因此实际上是将序列 t 1 , t 2 , … , t n t_1, t_2, \ldots, t_n t1,t2,…,tn 划分为若干连续段,每个段对应一节课,段内主题时间总和不超过 L L L,且段数尽可能少,然后在段数最少的前提下最小化所有段的不满指数之和。
显然,最小可能的课程数可以通过贪心得到:按顺序累加主题时间,一旦加上下一个主题会超过 L L L,则必须开启新的一节课。这种贪心策略可以得到最小的课程数,因为每节课容量有限且主题不可拆分,推迟开启新课不会减少总段数(相反可能增加)。因此我们可以先用贪心求出最小课程数 K K K。
接下来,我们需要在恰好使用 K K K 节课的前提下,最小化总不满指数。这可以通过动态规划解决。
设 DP [ i ] [ j ] \textit{DP}[i][j] DP[i][j] 表示前 i i i 节课覆盖前 j j j 个主题时的最小总不满指数( i i i 节课恰好用完)。那么转移时考虑最后一节课包含哪些主题:若第 i i i 节课覆盖主题 k + 1 k+1 k+1 到 j j j,则这些主题的时间总和 s = ∑ p = k + 1 j t p s = \sum_{p=k+1}^{j} t_p s=∑p=k+1jtp 必须满足 s ≤ L s \le L s≤L。于是:
DP [ i ] [ j ] = min k < j , ∑ p = k + 1 j t p ≤ L ( DP [ i − 1 ] [ k ] + cost ( s ) ) \textit{DP}[i][j] = \min_{k < j, \sum_{p=k+1}^{j} t_p \le L} \left( \textit{DP}[i-1][k] + \texttt{cost}(s) \right) DP[i][j]=k<j,∑p=k+1jtp≤Lmin(DP[i−1][k]+cost(s))
其中 cost ( s ) \texttt{cost}(s) cost(s) 表示使用时间为 s s s 时该节课的不满指数。初始条件 DP [ 0 ] [ 0 ] = 0 \textit{DP}[0][0] = 0 DP[0][0]=0,其余 ∞ \infty ∞。最终答案为 DP [ K ] [ n ] \textit{DP}[K][n] DP[K][n]。
由于 n ≤ 1000 n \le 1000 n≤1000, K ≤ n K \le n K≤n,直接三重循环( i , j , k i, j, k i,j,k)复杂度为 O ( n 3 ) O(n^3) O(n3),可能较慢,但实际数据可通过。也可以优化为预处理前缀和,在转移时内层循环枚举 k k k,但由于 n n n 较小( 1000 1000 1000), O ( n 3 ) O(n^3) O(n3) 在本题中可接受(实际运行时间 0.160 0.160 0.160 秒)。
在代码中,使用的是记忆化搜索(
dfs
\texttt{dfs}
dfs)实现动态规划,状态为 (lectures, topics) 表示剩余 lectures 节课覆盖前 topics 个主题的最小不满指数,递归地从后向前划分。
解题思路
步骤一:贪心求最小讲座数
按顺序累加主题时间,若加入下一个主题会导致当前讲座超过
L
L
L,则另起一节。记录讲座数 ML。
步骤二:动态规划求最小不满指数
采用记忆化搜索,函数 dfs(lectures, topics) 返回用 lectures 节课覆盖前 topics 个主题的最小不满指数。
- 若
lectures == 0:当topics == 0时返回 0 0 0,否则返回 ∞ \infty ∞(无效状态)。 - 否则,枚举最后一节课覆盖的主题范围:假设最后一节课包含主题
t, t+1, ..., topics(其中t从topics递减到1),累计时间elapsed += ti[t],一旦elapsed > L则停止枚举(因为时间只增不减,继续往前加只会更大)。对于每个可行的t,计算best = dfs(lectures-1, t-1),若best != INF,则更新当前状态值为min(best + getDI(elapsed))。
记忆化数组 DI[lectures][topics] 存储结果,visited 标记是否计算过。
步骤三:输出
先输出 Case X:,再输出最小讲座数 ML,然后输出 dfs(ML, N)。
注意:每组数据之间输出空行,但末尾不需要多余空行。
复杂度分析
- 贪心求最小讲座数: O ( n ) O(n) O(n)。
- 记忆化搜索:状态数为
K
×
n
K \times n
K×n,每个状态枚举可能的最后一段,总转移次数约为
O
(
n
3
)
O(n^3)
O(n3)(因为对于每个状态
(i, j),内部循环可能枚举 O ( j ) O(j) O(j) 次)。 n n n 最大 1000 1000 1000, n 3 = 10 9 n^3 = 10^9 n3=109 过大,但实际由于elapsed超过 L L L 会提前终止,且 L L L 通常不大( L ≤ 500 L \le 500 L≤500),平均每节课包含的主题数有限,因此实际运行很快。最坏情况下若所有 t i = 1 t_i = 1 ti=1,则每节课可包含 L L L 个主题,状态数 K × n K \times n K×n,转移中内层循环最多 L L L 次,总复杂度 O ( n ⋅ L ⋅ K ) ≈ O ( n 2 ⋅ L ) O(n \cdot L \cdot K) \approx O(n^2 \cdot L) O(n⋅L⋅K)≈O(n2⋅L),在 n = 1000 , L = 500 n=1000, L=500 n=1000,L=500 时约为 5 × 10 8 5 \times 10^8 5×108,但仍可通过(UVa 运行时间 0.160 s 0.160s 0.160s),说明实际数据更友好。 - 空间复杂度: O ( K ⋅ n ) O(K \cdot n) O(K⋅n),即 O ( n 2 ) O(n^2) O(n2), n = 1000 n=1000 n=1000 时可接受。
代码实现
// Scheduling Lectures
// UVa ID: 607
// Verdict: Accepted
// Submission Date: 2017-06-25
// UVa Run Time: 0.160s
//
// 版权所有(C)2017,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int INF = 1 << 30, MAXN = 1024;
int N, L, C, ML, ti[MAXN], DI[MAXN][MAXN], visited[MAXN][MAXN];
int getDI(int m) {
int t = L - m;
if (t == 0) return 0;
if (1 <= t && t <= 10) return -C;
return (t - 10) * (t - 10);
}
int dfs(int lectures, int topics) {
if (visited[lectures][topics]) return DI[lectures][topics];
int *di = &DI[lectures][topics];
if (lectures == 0) return topics ? INF : 0;
else {
*di = INF;
int elapsed = 0;
for (int t = topics; t >= 1; t--) {
elapsed += ti[t];
if (elapsed > L) break;
int best = dfs(lectures - 1, t - 1);
if (best != INF) *di = min(best + getDI(elapsed), *di);
}
visited[lectures][topics] = 1;
return *di;
}
}
int main() {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
int cases = 0;
while (cin >> N, N > 0) {
ML = 1;
cin >> L >> C;
memset(visited, 0, sizeof(visited));
for (int i = 1, elapsed = 0; i <= N; i++) {
cin >> ti[i];
elapsed += ti[i];
if (elapsed > L) {
ML++;
elapsed = ti[i];
}
}
if (cases++ > 0) cout << '\n';
cout << "Case " << cases << ":\n";
cout << "Minimum number of lectures: " << ML << '\n';
cout << "Total dissatisfaction index: " << dfs(ML, N) << '\n';
}
return 0;
}
总结
本题是典型的双目标优化问题,通过贪心求解第一目标,再通过动态规划求解第二目标。关键点包括:
- 主题的顺序性和不可分割性决定了划分必须是连续的。
- 先独立求解最小讲座数(贪心),因为该目标与不满指数无关,且贪心最优。
- 在固定讲座数下,使用动态规划求解最小不满指数,注意空闲时间不满指数的分段函数。
- 记忆化搜索实现简洁,但需注意状态无效时的 ∞ \infty ∞ 处理。
该解法在时间上足够高效,思路清晰,适合作为动态规划与贪心结合的教学例题。

705

被折叠的 条评论
为什么被折叠?



