UVa 607 Scheduling Lectures

题目描述

你正在讲授一门课程,需要覆盖 n n n 个主题( 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000)。每节课时长为 L L L 分钟( 1 ≤ L ≤ 500 1 \le L \le 500 1L500),每个主题需要 t i t_i ti 分钟( 1 ≤ t i ≤ L 1 \le t_i \le L 1tiL)。对于每个主题,你必须决定在哪一节课中讲授。有两个限制:

  1. 每个主题必须完整地在一节课内讲授,不能分割到两节课中。
  2. 主题必须按顺序讲授,即主题 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=Lm。不满指数为:

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,(t10)2,t=0,1t10,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 sL。于是:

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+1jtpLmin(DP[i1][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 n1000 K ≤ n K \le n Kn,直接三重循环( 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(其中 ttopics 递减到 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 L500),平均每节课包含的主题数有限,因此实际运行很快。最坏情况下若所有 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(nLK)O(n2L),在 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(Kn),即 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 处理。

该解法在时间上足够高效,思路清晰,适合作为动态规划与贪心结合的教学例题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值