题解链接:https://mp.weixin.qq.com/s/b2qohQxoMJk8Kysswtc7mQ 
测评链接:https://niumacode.com/training/143 
 
1. 部门旅游 今年小师妹将再次组织大家进行一次精彩的部门旅游。为了确保每位员工能够顺利参与,现有三个不同的旅游团可供选择。每个旅游团的人数限制各不相同,员工可根据自己的喜好选择报名的旅游团。为了公平起见,系统将采用先到先得的原则进行处理。 报名规则如下:
每位员工可以根据喜好报名参加1到3个旅游团。  
报名时,如果首选的旅游团已满员,系统将自动尝试加入员工的次选旅游团。 
如果员工所选择的所有旅游团均已满员,则该员工无法参加此次旅游。 
 
输入描述
 
第一行包含一个整数 N,表示员工的数量(1 ≤ N ≤ 10000)。 
第二行包含三个整数,分别表示旅游团 A、B、C 的最大名额(1 ≤ A, B, C ≤ 10000)。 
接下来的 N 行,每行包含:
一个字符串,表示员工的 ID(由字母和数字组成,并且互不相同) ID 长度 <= 13。 
一个整数 X(1 ≤ X ≤ 3),表示该员工选择的旅游团数量。 
X 个字符(A/B/C),表示员工选择的旅游团的优先级,优先级高的选择在前。 
 
 
 
输出描述
 
按 A,B,C 三个团的顺序输出三行,每行一个整数,表示这个团最终的人数 a,接下来是 a 个字符串,表示进入这个团的员工 ID, 请按照报名顺序输出。 
 
示例 1
 
输入:
1 2 3 4 5 6 4 2 1 1 zhang01 2 A B chen01 1 B li02 3 B C A yang 2 B A 
 
输出:
1 2 3 2 zhang01 yang 1 chen01 1 li02 
 
示例 2
 
输入:
1 2 3 4 5 6 4 2 1 1 zhang01 2 A B chen01 2 A B li02 3 A B C yang 2 B A 
 
输出:
1 2 3 2 zhang01 chen01 1 li02 0 
 
代码:模拟
 
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 #include  <iostream>  #include  <vector>  #include  <string>  using  namespace  std;int  main ()   {    int  employeeCount;     cin >> employeeCount;          int  maxA, maxB, maxC;     cin >> maxA >> maxB >> maxC;          vector<string> groupA, groupB, groupC;          for  (int  i = 0 ; i < employeeCount; ++i) {         string employeeID;         int  choiceCount;         cin >> employeeID >> choiceCount;                  vector<char > choices (choiceCount)  ;         for  (auto & choice : choices) {             cin >> choice;         }                  for  (char  choice : choices) {             if  (choice == 'A'  && groupA.size () < maxA) {                 groupA.push_back (employeeID);                 break ;             }             else  if  (choice == 'B'  && groupB.size () < maxB) {                 groupB.push_back (employeeID);                 break ;             }             else  if  (choice == 'C'  && groupC.size () < maxC) {                 groupC.push_back (employeeID);                 break ;             }         }     }          cout << groupA.size ();     for  (const  string& id : groupA) {         cout << " "  << id;     }     cout << "\n" ;          cout << groupB.size ();     for  (const  string& id : groupB) {         cout << " "  << id;     }     cout << "\n" ;          cout << groupC.size ();     for  (const  string& id : groupC) {         cout << " "  << id;     }     cout << "\n" ;          return  0 ; } 
 
2. 战力极差的最小值 在《无主星渊》的竞技宇宙中,来自各个星系的顶尖战队齐聚一堂,准备迎战传说中的最终 BOSS。每个星系派出了最精锐的战队,共 n 支战队,每支战队有 m 名成员,每位成员的战力值分别为 $p_1, p_2, …, p_m$。为了组成最强的终极挑战队,你需要从每支战队中各选 1 名成员(共 n 人),但团队配合至关重要。经过无数次模拟战斗,联盟科学家发现:战力越均衡的团队,越能激发协同共鸣。因此,选拔规则如下:在所有可能的组队方案中,选择战力极差(最大值 - 最小值)最小的方案,确保团队以最平衡的状态迎战 BOSS。
你的任务:计算所有可能的组队方案中,战力极差的最小值。
输入描述
 
第一行两个正整数 $n (0 < n <= 3000)$、$m (0 < m <= 100)$,分别表示队伍数量与每只战队中的成员数量。 
之后 $n$ 行,每行输入 $m$ 个数字 $p_i (0 < p_i < 1e9)$,代表战队中成员的战力值。 
 
输出描述
 
示例 1
 
输入:
 
输出:
 
示例 2
 
输入:
1 2 3 4 3 4 10 15 24 26 0 9 12 20 5 18 22 30 
 
输出:
 
代码:滑动窗口
 
我们需要从 n 支战队中各选 1 名成员,组成一个 n 人团队。在所有可能的组队方案中,找到战力极差(最大值 - 最小值)最小的方案。
解题思路
暴力法不可行 :直接枚举所有可能的组合(共 $m^n$ 种)显然不现实,因为 n 和 m 可能较大(n ≤ 3000,m ≤ 100)。 
排序 + 滑动窗口 :
将每支战队的成员战力值排序。 
将所有战队的成员合并到一个列表,并记录每个成员所属的战队。 
对这个合并后的列表按战力值排序。 
使用滑动窗口,找到一个窗口,其中包含所有 n 支战队的至少一个成员,且窗口的极差最小。 
 
 
 
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 #include  <iostream>  #include  <vector>  #include  <algorithm>  #include  <climits>  #include  <unordered_map>  using  namespace  std;int  main ()   {    int  n, m;     cin >> n >> m;     vector<vector<int >> teams (n, vector <int >(m));     for  (int  i = 0 ; i < n; ++i) {         for  (int  j = 0 ; j < m; ++j) {             cin >> teams[i][j];         }         sort (teams[i].begin (), teams[i].end ());      }          vector<pair<int , int >> members;      for  (int  i = 0 ; i < n; ++i) {         for  (int  val : teams[i]) {             members.emplace_back (val, i);         }     }          sort (members.begin (), members.end ());     int  left = 0 ;     unordered_map<int , int > team_count;     int  min_diff = INT_MAX;     for  (int  right = 0 ; right < members.size (); ++right) {         int  team = members[right].second;         team_count[team]++;                  while  (team_count.size () == n) {             int  current_diff = members[right].first - members[left].first;             if  (current_diff < min_diff) {                 min_diff = current_diff;             }                          int  left_team = members[left].second;             team_count[left_team]--;             if  (team_count[left_team] == 0 ) {                 team_count.erase (left_team);             }             left++;         }     }     cout << min_diff << endl;     return  0 ; } 
 
3. 金矿采集 在《无主星渊》的太空战场中,玩家需操控飞船从起点 (S) 出发,在 n×m 的网格中以最短时间采集所有的金矿。飞船每次仅能向上下左右四个方向移动一个网格,金矿可以以任何先后顺序被采集,飞船到达金矿后可以选择立即采集也可以选择路过。一共有 k 个金矿,金矿初始的矿产值为 Xi,当飞船采集到 a(0<=a<=k) 个金矿后,每移动一步,所有未被采集的金矿都会减少 a 点矿产值,当金矿的矿产值减少到 1 的时候将不再减小。需要你帮玩家算一下,玩家最多可以采集到金矿的总价值。
网格包含以下元素:
#:不可穿越的障碍物 
.:可自由航行的太空区域 
1~5:表示 $k$ 个金矿的编号 
S:飞船起点 
 
输入描述
 
首行:n m k (1 ≤ n, m ≤ 50, 1≤ k ≤5) 
后续 $n$ 行,每行 $m$ 个字符表示 $n*m$ 的网格的信息 
最后一行是 $k$ 个整数,第 $i$ 个数表示编号为 $i$ (1<=i<=k) 的金矿的初始矿产值 Xi (1<= Xi <=1000) 
 
输出描述
 
玩家最多可以采集到的金矿总矿产值,数据保证所有金矿都可以到达  
 
示例 1
 
输入:
1 2 3 4 5 6 7 5 7 3 S.....3 ##..... ..##... ..1.... 2..#... 10 20 30 
 
输出:
 
示例 2
 
输入:
1 2 3 4 5 6 4 4 1 .... .##. .##. S##1 100  
 
输出:
 
示例 3
 
输入:
1 2 3 4 5 6 7 5 7 3 S.....3 ##..... ..##... ..1.... 2..#... 40 1 1 
 
输出:
 
代码:BFS + 全排列枚举
 
(1) 数据输入与预处理
读取网格地图,记录起点 S 和金矿的位置(保存到 positions 数组)。 
金矿编号为 1 到 goldCount,起点编号为 0。 
 
(2) BFS 计算最短路径
对每个金矿(包括起点)运行 BFS,计算到其他所有金矿的最短路径步数:
使用 distances[i][j] 表示从位置 i 到位置 j 的最短步数。 
通过BFS遍历地图,跳过障碍物和边界。 
 
 
 
(3) 全排列搜索最优顺序
生成所有金矿的排列顺序(1 到 goldCount的全排列)。 
对每种顺序计算总得分:
从起点(last = 0)出发。 
对于第 k 个金矿 order[k]:
累加步数惩罚:minus += stepCount * k。 
计算当前金矿得分:max(initialValues[order[k]] - minus, 1)。 
更新当前位置 last。 
 
 
记录所有顺序中的最大得分 maxTotal。 
 
 
 
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include  <bits/stdc++.h>  using  namespace  std;vector<string> grid; int  rows, cols, goldCount;const  int  INF = 0x3f3f3f3f ;int  main ()   {    ios::sync_with_stdio (0 );     cin.tie (0 );          cin >> rows >> cols >> goldCount;     grid.resize (rows);     for  (auto & row : grid) {         cin >> row;     }          vector<int > initialValues (goldCount + 1 )  ;     for  (int  i = 1 ; i <= goldCount; ++i) {         cin >> initialValues[i];     }          vector<pair<int , int >> positions (goldCount + 1 , {-1 , -1 });     pair<int , int > startPos;          for  (int  i = 0 ; i < rows; ++i) {         for  (int  j = 0 ; j < cols; ++j) {             if  (grid[i][j] == 'S' ) {                 startPos = {i, j};             }             else  if  (isdigit (grid[i][j])) {                 int  goldId = grid[i][j] - '0' ;                 positions[goldId] = {i, j};             }         }     }          auto  bfs = [](int  startX, int  startY) {         vector<vector<int >> dist (rows, vector <int >(cols, INF));         queue<pair<int , int >> q;         q.push ({startX, startY});         dist[startX][startY] = 0 ;                  vector<vector<int >> directions = {{0 , 1 }, {1 , 0 }, {0 , -1 }, {-1 , 0 }};                  while  (!q.empty ()) {             auto  [x, y] = q.front ();             q.pop ();                          for  (auto & dir : directions) {                 int  newX = x + dir[0 ];                 int  newY = y + dir[1 ];                                  if  (newX < 0  || newX >= rows || newY < 0  || newY >= cols) {                     continue ;                 }                 if  (grid[newX][newY] == '#'  || dist[newX][newY] != INF) {                     continue ;                 }                                  dist[newX][newY] = dist[x][y] + 1 ;                 q.push ({newX, newY});             }         }         return  dist;     };          positions[0 ] = startPos;     int  totalPoints = goldCount + 1 ;     vector<vector<int >> distances (totalPoints, vector <int >(totalPoints));          for  (int  i = 0 ; i < totalPoints; ++i) {         auto  dist = bfs (positions[i].first, positions[i].second);         for  (int  j = 0 ; j < totalPoints; ++j) {             distances[i][j] = dist[positions[j].first][positions[j].second];         }     }          vector<int > order (goldCount)  ;     iota (order.begin (), order.end (), 1 );     int  maxTotal = 0 ;          do  {         int  minus = 0 , total = 0 , last = 0 ;         for  (int  i = 0 ; i < goldCount; ++i) {             int  currentGold = order[i];             int  stepCount = distances[last][currentGold];             minus += stepCount * i;             total += max (initialValues[currentGold] - minus, 1 );             last = currentGold;         }         maxTotal = max (maxTotal, total);     } while  (next_permutation (order.begin (), order.end ()));          cout << maxTotal << endl;          return  0 ; } 
 
4. 篮球游戏 你正在篮球场上与其他玩家玩一场游戏。你需要站在看台边,用推车接住从看台上扔下来的篮球。
篮球上标有不同的积分,你接到后就获得了对应的积分。但是其中有一部分玩家他们会扔其他种类的球,如果你不小心接到了这些球,你就需要停在原地 3 秒。期间你只能等待时间过去,或者正好有球进入车筐中。如果你在停止期间又接到了非篮球的球类,不论之前你的停止时间还剩多少,它都会重新刷新为 3 秒。
所有的球类都会在1  秒的时间里下落 1 格高度,而你也可以在 1s 时间里向左或者向右移动一格,或者不动。当车筐与球重合时,表示你接到了球,你可以在一个位置同时接到多个球。
一开始,你可以选择任意的位置,那么你怎么规划你的移动路线,能够使得接到的球总积分最高呢?
输入描述
 
第一行有两个整数 n (1 <= n <= 100),m (1 <= m <= 1000),n 表示可以接到的球以及人可以站立的横向长度,m 表示扔出的球的总数 
接下来 m 行,每行四个整数 $v_i (0 <= v_i <= 1e5),x_i (0 <= x_i < n),y_i (0 < y_i <= 1000),t_i (0 < t_i <= 1000)$,$v_i$ 表示球的积分,$x_i$ 表示物品的横向坐标,$y_i$ 表示物品的初始高度,$t_i$ 表示物品开始掉落的时间。当接收到 $v_i==0$ 的球时,会使你困在原地 3 秒,如果此时已经处于被困住的状态,则时间会重置为 3 秒 
 
输出描述
 
示例 1
 
输入:
1 2 3 4 10 3 3 5 3 3 0 3 2 1 1 0 10 6 
 
输出:
 
示例 2
 
输入:
 
 输出:
 
代码:动态规划
 
这是一个动态规划问题,我们需要在时间和空间维度上规划接球路线,最大化获得的积分,同时处理”定身”状态的干扰。
时间处理:所有球的下落时间被预处理为 time_start + height,即球到达底部的时间。
状态表示:dp[pos][stun] 表示在位置 pos 且剩余定身时间为 stun 时的最大得分
定身状态有 4 种可能:0(无定身),1,2,3
状态转移:
无定身时:可以左移、右移或不动 
有定身时:只能不动,定身时间减 1 
接到非篮球 (v=0) 时:强制将定身时间设为 3 
 
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 84 85 86 87 88 89 90 91 92 93 94 95 96 #include  <bits/stdc++.h>  using  namespace  std;constexpr  int  MAX_TIME = 2000 ;constexpr  long  long  NEG_INF = -(1LL  << 60 );int  main ()   {    ios::sync_with_stdio (false );     cin.tie (nullptr );     int  positions, events;     cin >> positions >> events;          vector<vector<int >> scores (MAX_TIME + 1 , vector <int >(positions, 0 ));          vector<vector<bool >> traps (MAX_TIME + 1 , vector <bool >(positions, false ));     for  (int  i = 0 ; i < events; ++i) {         int  value, pos, height, time_start;         cin >> value >> pos >> height >> time_start;         int  landing_time = time_start + height;         scores[landing_time][pos] += value;         if  (value == 0 ) {               traps[landing_time][pos] = true ;         }     }          vector<vector<long  long >> dp (positions, vector <long  long >(4 , NEG_INF));          for  (int  i = 0 ; i < positions; ++i) {         dp[i][0 ] = 0 ;     }     long  long  answer = 0 ;     for  (int  time = 0 ; time <= MAX_TIME; ++time) {         vector<vector<long  long >> next_dp (positions, vector <long  long >(4 , NEG_INF));         for  (int  pos = 0 ; pos < positions; ++pos) {             for  (int  stun = 0 ; stun <= 3 ; ++stun) {                 long  long  curr_score = dp[pos][stun];                 if  (curr_score == NEG_INF) continue ;                 int  new_stun = (stun == 0 ) ? 0  : stun - 1 ;                                  if  (stun == 0 ) {                     if  (pos > 0 ) {                         next_dp[pos - 1 ][new_stun] = max (next_dp[pos - 1 ][new_stun],                                                          curr_score + scores[time][pos - 1 ]);                     }                     if  (pos + 1  < positions) {                         next_dp[pos + 1 ][new_stun] = max (next_dp[pos + 1 ][new_stun],                                                          curr_score + scores[time][pos + 1 ]);                     }                 }                                  next_dp[pos][new_stun] = max (next_dp[pos][new_stun],                                              curr_score + scores[time][pos]);             }         }                  for  (int  pos = 0 ; pos < positions; ++pos) {                          for  (int  stun = 0 ; stun <= 3 ; ++stun) {                 if  (next_dp[pos][stun] > answer) {                     answer = next_dp[pos][stun];                 }             }             if  (traps[time][pos]) {                                  long  long  max_stun3 = next_dp[pos][3 ];                 for  (int  stun = 0 ; stun < 3 ; ++stun) {                     if  (next_dp[pos][stun] != NEG_INF) {                         max_stun3 = max (max_stun3, next_dp[pos][stun]);                         next_dp[pos][stun] = NEG_INF;                     }                 }                 next_dp[pos][3 ] = max_stun3;                 if  (next_dp[pos][3 ] > answer) {                     answer = next_dp[pos][3 ];                 }             }         }         dp = move (next_dp);     }     cout << answer << "\n" ;     return  0 ; }