题解链接:https://mp.weixin.qq.com/s/DawyjMfoNpKxmRfqqJR9hg 
测评链接:https://niumacode.com/training/127 
 
1. 开发一个简单任务调度系统 你需要开发一个简单的任务调度系统,该系统按任务优先级调度,优先级范围是 $(0, 99)$,数值越小优先级越高。只有高优先级任务执行完成后,低优先级任务才能执行,同等优先级的任务按照 $FIFO$ 原则,先进入调度系统的任务会优先调度,当优先级任务执行时,如果新增高优先级任务,高优先级任务会抢占低优先级任务。
请你实现一个程序,模拟这个任务调度系统。
添加任务 :将一个新任务添加到任务调度系统。任务包含一个唯一ID(task_id)、优先级(priority)$[0,99]$,运行时间(time)$[1,10000]$。 
执行任务 :任务调度系统按照调度策略,调度任务并执行。调度系统调度任务,并消耗对应时间片,时间片范围 $[1,100000]$。 
 
程序需要处理以下类型的输入:
添加任务 add task_id priority time 
执行任务 run time 
 
注:
输入命令总行数不超过 10000 行 
run 命令可以有多个 
空行即命令结束 
 
Output 显示任务调度系统当前执行的任务 ID。若无任何任务,则显示 idle。
Sample 1 输入:
1 2 3 4 add 101 0 10 add 20 1 3 add 300 0 1 run 11 
 
输出:
 
样例 1 解释:
add 101 0 10:添加任务 101,其优先级为 0,运行时间为 0 个时间片 
add 20 1 3:添加任务 20,其优先级为 1,运行时间为 3 个时间片 
add 300 0 1:添加任务 30,其优先级为 0,运行时间为 1 个时间片 
run 11:调度系统调度任务并执行。首先调度任务 101,运行了 10 个时间片,任务完成。接下来调度任务 300(其优先级高于任务 20),运行了 1 个时间片,任务完成。此时消耗完全部运行时间片(即 11)。 
此时调度系统要运行的任务 id 即为 20 
 
Sample 2 输入:
 
输出:
 
样例 2 解释:
add 1 0 10:添加任务 1,优先级 0,运行时间为 10 个时间片 
run 11:调度系统调度任务,并运行 11 个时间片。选择任务 1 运行了 10 个时间片,任务 1 完成。无任务待调度。 
调度系统无任何任务,因此显示 idle。 
 
Solution (False) 
每执行一次 run 则输出一次结果
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include  <bits/stdc++.h>  using  namespace  std;struct  Task  {    int  id;     int  priority;     int  remain;   }; queue<Task> slots[100 ]; void  addTask (int  id, int  priority, int  time)   {    slots[priority].push ({id, priority, time}); } Task getNext ()   {    for  (int  p = 0 ; p < 100 ; ++p) {         if  (!slots[p].empty ()) {             Task t = slots[p].front ();             slots[p].pop ();             return  t;         }     }     return  {-1 , -1 , -1 }; } int  getCurrentTaskId ()   {    for  (int  p = 0 ; p < 100 ; ++p) {         if  (!slots[p].empty ()) {             return  slots[p].front ().id;         }     }     return  -1 ; } int  main ()   {    ios::sync_with_stdio (false );     cin.tie (nullptr );     string cmd;     while  (getline (cin, cmd)) {         if  (cmd.empty ())             break ;         stringstream ss (cmd)  ;         ss >> cmd;         if  (cmd == "add" ) {             int  id, pr, t;             ss >> id >> pr >> t;             addTask (id, pr, t);         } else  if  (cmd == "run" ) {             int  time;             ss >> time;             while  (time > 0 ) {                 Task cur = getNext ();                 if  (cur.id == -1 )                     break ;                   if  (cur.remain <= time) {                     time -= cur.remain;                   } else  {                     cur.remain -= time;                       time = 0 ;                     slots[cur.priority].push (cur);                   }             }             int  nid = getCurrentTaskId ();             if  (nid == -1 )                 cout << "idle" ;             else                  cout << nid;             cout << "\n" ;         }     }     return  0 ; } 
 
Solution (True) 
只输出最后一次 run
 
✅ 参考链接:https://codefun2000.com/p/P2972 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include  <bits/stdc++.h>  using  namespace  std;struct  Task  {    int  id;          int  prio;        int  rem;         int  order;   }; struct  Cmp  {    bool  operator () (const  Task &a, const  Task &b)  const   {         if  (a.prio != b.prio)             return  a.prio > b.prio;         return  a.order > b.order;     } }; int  main ()   {    ios::sync_with_stdio (false );     cin.tie (nullptr );     priority_queue<Task, vector<Task>, Cmp> pq;     string cmd;     int  next_order = 0 ;     vector<pair<string, vector<int >>> ops;          while  (getline (cin, cmd)) {         if  (cmd.empty ())             break ;         stringstream ss (cmd)  ;         string op;         ss >> op;         if  (op == "add" ) {             int  id, p, t;             ss >> id >> p >> t;             ops.push_back ({op, {id, p, t}});         } else  if  (op == "run" ) {             int  T;             ss >> T;             ops.push_back ({op, {T}});         }     }          int  last_run_idx = -1 ;     for  (int  i = 0 ; i < (int )ops.size (); i++) {         if  (ops[i].first == "run" )             last_run_idx = i;     }     for  (int  i = 0 ; i < (int )ops.size (); i++) {         auto  &op = ops[i];         if  (op.first == "add" ) {                          pq.push ({op.second[0 ], op.second[1 ], op.second[2 ], next_order++});         } else  {             int  T = op.second[0 ];             while  (T > 0  && !pq.empty ()) {                 Task t = pq.top ();                 pq.pop ();                 if  (t.rem <= T) {                     T -= t.rem;                   } else  {                     t.rem -= T;                       T = 0 ;                     pq.push (t);                   }             }                          if  (i == last_run_idx) {                 if  (pq.empty ())                     cout << "idle\n" ;                 else                      cout << pq.top ().id << "\n" ;             }         }     }     return  0 ; } 
 
2. 地震救灾路线 某市发生地震,为了尽快将救援物质输送到受灾乡镇,需要你设计出从救援物质集结点(有仅有一个)到某一个受灾乡镇的最短线路
应急部门通过无人机助察了受灾地区地形,提供了各乡镇之间以及乡镇到救援物质集结点的距离,请你算出救援物质集结点到受灾多镇的最短路径。
第一行,N,受灾乡镇个数,3<=N<=20
第二行至第 N+2 行,救援物质集结点以及各乡镇之间的距离矩阵(即 N+1 个节点之间的相互距离矩阵),距离取值范围是 $[0,100]$。序号 0 的节点表示救援物质集结点,序号 1 ~ N 的节点表示各个受灾乡镇。0 表示两个节点不相邻。
第 N+3 行,m,要抵达的乡镇序号(范围 1~N)
Output 从物质集结点(节点 0)到乡镇 m(节点 m)的最短路径长度
Sample 1 输入:
1 2 3 4 5 6 7 8 5 0 5 13 0 0 0 5 0 12 0 75 0 13 12 0 25 0 0 0 0 25 0 35 20 0 75 0 35 0 40 0 0 0 20 40 0 3 
 
输出:
 
样例 1 解释:
从 0 到 3 的最短路径为 $0-2-3$,长度为 $13 + 25 = 38$
Sample 2 输入:
1 2 3 4 5 6 7 8 5 0 5 13 0 0 0 5 0 12 0 75 0 13 12 0 25 0 0 0 0 25 0 35 20 0 75 0 35 0 40 0 0 0 20 40 0 5 
 
输出:
 
样例 2 解释:
从 0 到 5 的最短路径为 $0-2-3-5$,长度为 $13+25+20=58$
Solution ✅ 参考链接:https://codefun2000.com/p/P2973 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include  <bits/stdc++.h>  using  namespace  std;int  main ()   {    int  N;     cin >> N;      int  total = N + 1 ;     vector<vector<int >> d (total, vector <int >(total));          for  (int  i = 0 ; i < total; i++) {         for  (int  j = 0 ; j < total; j++) {             cin >> d[i][j];         }     }     int  m;     cin >> m;      const  int  INF = 1e9 ;     vector<int > dist (total, INF)  ;     vector<bool > vis (total, false )  ;     dist[0 ] = 0 ;           for  (int  i = 0 ; i < total; i++) {         int  u = -1 , minDist = INF;                  for  (int  j = 0 ; j < total; j++) {             if  (!vis[j] && dist[j] < minDist) {                 u = j;                 minDist = dist[j];             }         }         if  (u == -1 ) break ;          vis[u] = true ;                  for  (int  v = 0 ; v < total; v++) {             if  (!vis[v] && d[u][v] > 0  && dist[v] > dist[u] + d[u][v]) {                 dist[v] = dist[u] + d[u][v];             }         }     }     cout << dist[m] << endl;      return  0 ; } 
 
补充|Dijkstra 算法 Dijkstra 算法是一种求解非负权图 上单源最短路径的算法。
算法思路‼️ 将结点分成两个集合:已确定最短路长度的点集(记为 $S$ 集合)的和未确定最短路长度的点集(记为 $T$ 集合)。一开始所有的点都属于 $T$ 集合。
初始化 $dis(s) = 0$,其他点的 $dis$ 均为 $+∞$。
然后重复这些操作:
从 $T$ 集合中,选取一个最短路长度最小的结点,移到 $S$ 集合中。 
对那些刚刚被加入 $S$ 集合的结点的所有出边执行松弛操作。 
 
直到 $T$ 集合为空,算法结束。
时间复杂度 朴素的实现方法为每次「操作 2」执行完毕后,直接在 $T$ 集合中暴力寻找最短路长度最小的节点。「操作 2」总时间复杂度为 $O(m)$,「操作 1」总时间复杂度为 $O(n^2)$,全过程的时间复杂度为 $O(n^2+m)=O(n^2)$。
可以用「堆 」来优化这一过程:每松弛一条边 $(u,v)$,就将 $v$ 插入堆中(如果 $v$ 已经在堆中,直接执行 Decrease-key),「操作 1」直接取堆顶节点即可。共计 $O(m)$ 次 Decrease-key,$O(n)$ 次 pop,堆优化能做到的最优复杂度为 $O(nlogn+m)$。特别地,可以用优先队列 priority_queue 维护,此时无法执行 Decrease-key 操作,但可以通过每次松弛时重新插入该节点,且弹出时检查该节点是否已被松弛过,若是则跳过 ,复杂度 $O(mlogn)$,优点是实现比较简单。
这里的堆也可以用线段树实现,复杂度为 $O(mlogn)$,在一些特殊的非递归线段树实现下,该做法常数比堆更小。并且线段树支持的操作更多,在一些特殊图问题上只能用线段树来维护。
✅ 在稀疏图(邻接矩阵)中,$m=O(n)$,堆优化的 Dijkstra 算法具有较大的效率优势;
✅ 而在稠密图(邻接图)中,$m=O(n^2)$,这时候使用朴素实现更优。
代码实现 
这里同时给出 $O(n^2)$ 的暴力做法实现和 $O(m·logm)$ 的优先队列做法实现。
 
dijkstra 算法推荐练习题:
1️⃣ 朴素实现|稠密图(邻接图)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct  edge  {  int  v, w; }; vector<edge> e[MAXN]; int  dis[MAXN], vis[MAXN];void  dijkstra (int  n, int  s)   {  memset (dis, 0x3f , (n + 1 ) * sizeof (int ));   dis[s] = 0 ;   for  (int  i = 1 ; i <= n; i++) {     int  u = 0 , mind = 0x3f3f3f3f ;     for  (int  j = 1 ; j <= n; j++)       if  (!vis[j] && dis[j] < mind) u = j, mind = dis[j];     vis[u] = true ;     for  (auto  ed : e[u]) {       int  v = ed.v, w = ed.w;       if  (dis[v] > dis[u] + w) dis[v] = dis[u] + w;     }   } } 
 
2️⃣ 优先队列实现|稀疏图(邻接矩阵)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct  edge  {  int  v, w; }; struct  node  {  int  dis, u;   bool  operator >(const  node& a) const  { return  dis > a.dis; } }; vector<edge> e[MAXN]; int  dis[MAXN], vis[MAXN];priority_queue<node, vector<node>, greater<node>> q; void  dijkstra (int  n, int  s)   {  memset (dis, 0x3f , (n + 1 ) * sizeof (int ));   memset (vis, 0 , (n + 1 ) * sizeof (int ));   dis[s] = 0 ;   q.push ({0 , s});   while  (!q.empty ()) {     int  u = q.top ().u;     q.pop ();     if  (vis[u]) continue ;     vis[u] = 1 ;     for  (auto  ed : e[u]) {       int  v = ed.v, w = ed.w;       if  (dis[v] > dis[u] + w) {         dis[v] = dis[u] + w;         q.push ({dis[v], v});       }     }   } } 
 
3. 云计算服务器 GPU 分配 某云计算服务商为客户提供 M 数量 GPU 核数的 GPU 分时租用服务,租用计费规则为:允许客户在每个时间单位按需租用不同的 GPU 核数,每个时间单位每个 GPU 核数的费用为 R。现有 N 个客户,每个客户有多个不重叠时间租用一定数量的 GPU 核数租用需求。对于有需求的客户,服务商可选择签约或不签约,若选择签约则需要满足租用需求中的所有时间段所需的 GPU 核数。
为了实现租金最大化收益,服务商需在确保任意时间单位内分配的 GPU 核数总数不超过 M 的基础上选择与哪些客户签约租用协议。
请输出租金最大化收益下的租金最大值。
第一行为 M、N、R 的数值,依次用空格隔开,输入格式为 M N R。
从第二行开始,每行为一个客户的租用需求,共 N 行。每行的第一个数字为该客户端的时间段个数 timeSegmentNum,后续为 timeSegmentNum 个时间段及所需的 GPU 核数,时间段个数 timeSegmentNum 与时间段之间、多个时间段之间均用空格分割,同一个客户多个时间段已按起始时间增序排序给出。同个客户多个时间段不会重叠。同一个客户多个时间段已按起始时间增序排序给出。
每个时间段及所需的 GPU 核数格式为 start 起始时间编号:end 结束时间编号:needcores 该时间段所需的 GPU 核数。
变量取值范围
$1<=M<=100000$ $1<=N<=10$ $1<=R<=10$ $0<= start<= end<= 10^9$ $0<= start<= end<= 10^9$ $1<= needCores<= 10000$ $1<= timeSegmentNum <= 100$
客户的租用需求样例 $2$、$0:0:1$、$3:6:10$ 的含义是共有 2 个时间段,0:0:1 表示在第 0 个时间单位需要 1 个 GPU 核,3:6:10 表示从 3 到 6 的时间单位(包含 3 和 6)每个时间单位均需 10 个 GPU 核。
图例为:
Output 总租金最大值。如果任意一个客户的需求都无法满足,则输出 0
Sample 1 输入:
1 2 3 4 10 3 2 2 0:8:5 9:23:10 2 0:8:5 9:18:10 1 0:8:5 
 
输出:
 
样例 1 解释:
共 3 个客户。
由于第一个客户和第二个客户在 $9:18$ 时间范围段内总核数为 20 超过了 10,所以无法同时接受。
最大日租金方案为:接纳第一个客户和第三个客户的需求。
第一个客户共需要的GPU核数为 $9 * 5 + 15*10=195$
第三个客户共需要的GPU核数为 $9 * 5=45$
Sample 2 输入:
 
输出:
 
样例 2 解释:
最大 GPU 核数为 10,共 2 个客户。
第一客户和第二个客户在3时间点,总核数为 12 超过了 10,所以无法同时接受。
第一个客户共需要的GPU核数为 $4 * 6=24$
第二个客户共需要的GPU核数为 $8 * 6=48$
为满足最大租金,采纳第二个客户,最大租金值为(48)* 1=48
Sample 3 输入:
 
输出:
 
样例 3 解释:
最大 GPU 核数为 10,共 1 个客户。 在 $0-5$ 时间段需要 20 个 GPU 核数,无法满足。
Sample 4 输入:
1 2 10000 1 10 1 0:1000000000:10000 
 
输出:
 
样例 4 解释:
最大 GPU 核数为 10000,共 1 个客户。
客户在 $0-100000000$ 时间段需要 10000 个GPU核数,可以满足。
租金最大值为 1000000000100000
Solution ✅ 参考链接:https://codefun2000.com/p/P2974 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include  <bits/stdc++.h>  using  namespace  std;using  ll = long  long ;int  main ()   {    ios::sync_with_stdio (false );     cin.tie (NULL );          ll M; int  N; ll R;     cin >> M >> N >> R;          vector<vector<tuple<ll,ll,ll>>> segs (N);     for  (int  i = 0 ; i < N; i++) {         int  t; cin >> t;         while  (t--) {             ll s, e, c;             char  ch;             cin >> s >> ch >> e >> ch >> c;               segs[i].emplace_back (s, e, c);         }     }     ll ans = 0 ;          for  (int  mask = 0 ; mask < (1 <<N); mask++) {         vector<ll> xs;         ll W = 0 ;                  for  (int  i = 0 ; i < N; i++) if  (mask & (1 <<i)) {             for  (auto  &seg: segs[i]) {                 ll s,e,c; tie (s,e,c) = seg;                 xs.push_back (s);                 xs.push_back (e+1 );                 W += (e - s + 1 ) * c;             }         }         if  (xs.empty ()) continue ;                  sort (xs.begin (), xs.end ());         xs.erase (unique (xs.begin (), xs.end ()), xs.end ());         vector<ll> diff (xs.size()+1 )  ;                  for  (int  i = 0 ; i < N; i++) if  (mask & (1 <<i)) {             for  (auto  &seg: segs[i]) {                 ll s,e,c; tie (s,e,c) = seg;                 int  l = lower_bound (xs.begin (), xs.end (), s) - xs.begin ();                 int  r = lower_bound (xs.begin (), xs.end (), e+1 ) - xs.begin ();                 diff[l] += c;                 diff[r] -= c;             }         }                  ll cur = 0 ;         bool  ok = true ;         for  (int  i = 0 ; i+1  < (int )xs.size (); i++) {             cur += diff[i];             if  (cur > M) { ok = false ; break ; }         }         if  (ok) ans = max (ans, W * R);     }     cout << ans;     return  0 ; }