← 返回题解列表

P1069 闲时自可期 官方题解

【部分分:测试点 1∼41\sim 41∼4】

  • n,k20n,k\leq20 时,可以枚举记录员每次空闲且收到任务时选择哪一项任务。确定选择后,忙碌期间开始的任务全部跳过,再继续处理下一次空闲时刻。
  • 每个任务至多产生一次选择,直接枚举的复杂度为指数级。检查一组选择需要 O(n+k)\mathcal O(n+k) 的时间。
  • 这个做法保留了题目中的强制选择规则。重复计算来自相同的“重新空闲时刻”,因此后续只需要保存这些时刻的最优答案。

【部分分:测试点 5∼85\sim 85∼8】

  • 有任务开始的时刻不超过 2020 个,并且每个时刻至多有两项任务。我们把任务按照开始时刻排序,递归处理记录员下一次空闲后遇到的第一个事件时刻。
  • 忙碌期间的事件不产生分支。只有空闲时同时出现两项任务时,递归才分成两支,因此分支层数不超过 2020
  • 时间复杂度为 O(220+klogk)\mathcal O(2^{20}+k\log k)。这说明真正有用的状态是“从某个时刻重新开始安排”,而不是已经选择过哪些任务。

【部分分:测试点 9∼129\sim 129∼12】

  • 定义 fif_i 表示第 ii 分钟开始时记录员空闲,从第 ii 分钟到工作日结束最多能够得到多少分钟闲时。边界为 fn+1=0f_{n+1}=0
  • 若第 ii 分钟没有任务开始,这一分钟一定是闲时,所以 fi=fi+1+1f_i=f_{i+1}+1。否则必须选择一项从第 ii 分钟开始的任务,于是
fi=maxpj=ifi+tj.f_i=\max_{p_j=i} f_{i+t_j}.
  • 直接对每个 ii 扫描全部任务即可实现,时间复杂度为 O(nk)\mathcal O(nk),空间复杂度为 O(n)\mathcal O(n)

【部分分:测试点 13∼1513\sim 1513∼15】

  • 特殊性质保证每项任务结束后立刻到达另一个事件时刻,或者已经到达第 n+1n+1 分钟。因此任务转移的端点都在事件集合中。
  • 将全部开始时刻与 n+1n+1 排序,只在这些时刻保存 ff。两个相邻事件之间没有任务开始,其中每一分钟都直接贡献闲时。
  • 对每项任务只做一次转移,排序后时间复杂度为 O(klogk)\mathcal O(k\log k),空间复杂度为 O(k)\mathcal O(k)。完整数据只需要进一步消去事件压缩的限制。

【正解】

  • 考虑记录员在第 ii 分钟开始时恰好空闲。此前没有处理的任务已经交给其他人,此后也不会重新出现,所以过去做过哪些选择已经不再重要。

  • 从这一刻开始,后续安排只由当前时刻 ii 决定。定义 fif_i 表示第 ii 分钟开始时记录员空闲,从第 ii 分钟至工作日结束最多能够得到的闲时。

  • n+1n+1 分钟已经越过工作日,不再产生闲时,因此边界为 fn+1=0f_{n+1}=0

  • JiJ_i 为所有在第 ii 分钟开始的任务。若 JiJ_i 为空,这一分钟必然成为闲时,下一分钟仍然空闲,所以 fi=fi+1+1f_i=f_{i+1}+1

  • JiJ_i 非空,题目要求必须选择其中一项。选择持续 tt 分钟的任务后,这 tt 分钟都不会产生闲时,并在第 i+ti+t 分钟重新空闲。

  • 因而完整转移为

fi={fi+1+1,Ji=,maxtJifi+t,Ji.f_i= \begin{cases} f_{i+1}+1, & J_i=\varnothing,\\[3pt] \displaystyle\max_{t\in J_i}f_{i+t}, & J_i\neq\varnothing. \end{cases}
  • 转移只会从 fif_i 访问下标更大的状态,所以按照 i=n,n1,,1i=n,n-1,\ldots,1 的顺序计算即可。
  • 预先把每项任务的持续时间放入对应的 JpiJ_{p_i}。每一分钟只处理一次,每项任务也只会在计算其开始时刻时被枚举一次。
  • 答案为 f1f_1。时间复杂度为 O(n+k)\mathcal O(n+k),空间复杂度为 O(n+k)\mathcal O(n+k)。多组数据之间需要清空本组使用过的 JiJ_i,并重新设置 fn+1=0f_{n+1}=0

【参考代码】

/*Author:EhundateghDate:2026/8/19Name:rest.cppYou steal,I kill.*/#include <vector>#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 200010using namespace std; int c,T,n,k,Dp[MAXN];vector <int> Job[MAXN]; void Solve() {    scanf("%d%d",&n,&k);    int In1,In2;    for (int i=1;i<=k;i++) {        scanf("%d%d",&In1,&In2);        Job[In1].push_back(In2);    }    Dp[n+1]=0;    for (int i=n;i>=1;i--) {        if (Job[i].empty()) Dp[i]=Dp[i+1]+1;        else {            Dp[i]=0;            for (int j=0;j<(int)Job[i].size();j++) {                Dp[i]=max(Dp[i],Dp[i+Job[i][j]]);            }        }    }    printf("%d\n",Dp[1]);    for (int i=1;i<=n;i++) Job[i].clear();    return;} int main() {    scanf("%d%d",&c,&T);    while (T-->0) Solve();    return 0;}