1 条题解

  • 1
    @ 2025-4-8 17:23:43

    题目分析

    题意简述

    有一个懒惰的工人,需要处理一组编号为 11nn 的任务。每个任务 ii 有其处理时间 tit_i、到达时间 aia_i 和截止时间 did_i,且 tit_iaia_idid_i 均为非负整数。任务具有严格的截止时间,即任务 ii 只能在区间 Ii=[ai,di]I_i = [a_i, d_i] 内执行。工人一次只能执行一个任务,且任务一旦开始就不能中断。当一个任务完成后,如果有其他可执行的任务,需立即开始执行。若没有可执行任务,工人处于空闲状态,有新任务到达时则马上开始执行。题目要求找出工人执行任务的最小总时间。

    输入

    • 输入包含 TT 个测试用例,TT 的值在第一行给出。
    • 每个测试用例的第一行是任务数量 nn0n1000 \leq n \leq 100)。
    • 接下来 nn 行,每行包含三个整数,分别为任务 ii 的处理时间 tit_i1ti201 \leq t_i \leq 20)、到达时间 aia_i0ai2500 \leq a_i \leq 250)和截止时间 did_i1di2501 \leq d_i \leq 250)。

    输出

    每个测试用例输出一行,包含工人执行任务的最小总时间。


    解题思路

    动态规划

    本题采用动态规划(DP)来解决。设 dp[tim]dp[tim] 表示从时间 timtim 开始,工人执行任务的最小总时间。

    状态转移

    • 边界条件:当 timmaxitim \geq maxi 时(maximaxi 是所有任务截止时间的最大值),dp[maxi]=0dp[maxi] = 0,因为此时没有任务可执行。
    • 无可用任务情况:如果在时间 timtim 没有可执行的任务,即 v[tim].size()==0v[tim].size() == 0,则 dp[tim]=DP(tim+1)dp[tim] = DP(tim + 1),也就是直接转移到下一个时间点。
    • 有可用任务情况:如果在时间 timtim 有可执行的任务,遍历所有可执行任务 toto,状态转移方程为 dp[tim]=min(dp[tim],DP(tim+t[to])+t[to])dp[tim] = min(dp[tim], DP(tim + t[to]) + t[to]),即尝试执行每个可执行任务,取最小的总时间。

    预处理

    在进行动态规划之前,需要进行一些预处理:

    • 找出所有任务截止时间的最大值 maximaxi
    • 对于每个任务 ii,将其添加到所有满足 aikditia_i \leq k \leq d_i - t_i 的时间点 kk 对应的任务列表 v[k]v[k] 中,表示在时间点 kk 可以开始执行任务 ii

    代码实现

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    
    using namespace std;
    
    const int SIZE = 1280;
    int cas,n,maxi;
    int t[SIZE],a[SIZE],d[SIZE];
    int dp[SIZE<<1];
    bool vis[SIZE<<1];
    vector <int> v[SIZE<<1];
    
    int DP(int tim)
    {
        if(vis[tim])
            return dp[tim];
        if(tim >= maxi)
        {
            dp[maxi] = 0;
            return 0;
        }
        if(v[tim].size() == 0)
            dp[tim] = DP(tim+1);
        else
        {
            for(int i=0; i<(int)v[tim].size(); i++)
            {
                int to = v[tim][i];
                dp[tim] = min(dp[tim],DP(tim+t[to])+t[to]);
            }
        }
        vis[tim] = true;
        return dp[tim];
    }
    
    int main()
    {
        scanf("%d",&cas);
        while(cas--)
        {
            scanf("%d",&n);
            for(int i=0; i<256; i++)
                v[i].clear();
            maxi = 0;
            for(int i=1; i<=n; i++)
            {
                scanf("%d%d%d",&t[i],&a[i],&d[i]);
                for(int k=a[i]; k<=d[i]-t[i]; k++)
                    v[k].push_back(i);
                if(d[i] > maxi)
                    maxi = d[i];
            }
            for(int i=0; i<SIZE; i++)
                dp[i] = 0xfffffff;
            for(int i=maxi; i<SIZE; i++)
                dp[i] = 0;
            memset(vis,0,sizeof(vis));
            DP(0);
            printf("%d\n",dp[0]);
        }
        return 0;
    }    
    

    代码说明

    1. 全局变量
      • SIZE:定义数组大小。
      • cas:测试用例数量。
      • n:每个测试用例中的任务数量。
      • maxi:所有任务截止时间的最大值。
      • t[i]:任务 ii 的处理时间。
      • a[i]:任务 ii 的到达时间。
      • d[i]:任务 ii 的截止时间。
      • dp[tim]:从时间 timtim 开始,工人执行任务的最小总时间。
      • vis[tim]:标记时间 timtim 的状态是否已经计算过。
      • v[tim]:存储在时间 timtim 可以开始执行的任务列表。
    2. DP 函数
      • DP(int tim):递归计算从时间 timtim 开始的最小总时间。
      • vis[tim]vis[tim]true,说明该状态已经计算过,直接返回 dp[tim]dp[tim]
      • timmaxitim \geq maxi,则 dp[maxi]=0dp[maxi] = 0 并返回 00
      • v[tim].size()==0v[tim].size() == 0,则 dp[tim]=DP(tim+1)dp[tim] = DP(tim + 1)
      • 否则,遍历 v[tim]v[tim] 中的所有任务,更新 dp[tim]dp[tim] 为最小的 DP(tim+t[to])+t[to]DP(tim + t[to]) + t[to]
      • 最后标记 vis[tim]vis[tim]true 并返回 dp[tim]dp[tim]
    3. 主函数
      • 读取测试用例数量 cascas
      • 对于每个测试用例:
        • 读取任务数量 nn
        • 清空 vv 数组。
        • 读取每个任务的处理时间 tit_i、到达时间 aia_i 和截止时间 did_i,并将任务添加到相应的时间点列表中。
        • 找出所有任务截止时间的最大值 maximaxi
        • 初始化 dpdp 数组,将 dp[0]dp[0]dp[maxi1]dp[maxi - 1] 初始化为一个较大的值,dp[maxi]dp[maxi]dp[SIZE1]dp[SIZE - 1] 初始化为 00
        • 初始化 visvis 数组为 false
        • 调用 DP(0) 计算从时间 00 开始的最小总时间,并输出 dp[0]dp[0]
    • 1

    信息

    ID
    338
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者