乐于分享
好东西不私藏

拼多多2026暑假实习算法方向笔试题

拼多多2026暑假实习算法方向笔试题
一大早叔就收到DeepSeek发过来的 “V4正式版发布计划及计费调整说明”邮件。作为AI编程深度使用者,从这波调整(涨价)来看,国产的算力还要等华为发力。

拼多多2026暑假实习算法方向笔试题

第一题签到题,第二题普通题,第三题简单DP(需优化),第四题状压DP+矩阵快速幂(有一点难度)

第1题-多多的Boss挑战

题目内容

多多鸡在挑战强大的 BOSS,他有  个技能,每个技能具有:

  • 伤害值: 释放一次造成的伤害
  • 冷却时间: 释放后必须等待的时间(从释放开始计算)

注意:一旦释放某个技能,就必须周期性地持续释放它!

具体规则如下:

  • 你可以在  秒内使用 1 次初始释放一个技能;
  • 每次释放耗时 1 秒,期间不能释放其他技能
  • 之后必须在  持续释放该技能(只要不超时),其中  为冷却时间。如果  小于 1s,因为有释放时间,则两次释放之间仍然必须大于等于 1s。
  • 不能中途切换技能,也不能组合多个技能
  • 同一时间只能释放一个技能(所以不同技能的时间方案互斥)

战斗从时刻 0 开始,持续了  秒,你只能选择一个技能和一个起始时间(起始时间为 0 到  之间的正整数),进行周期性释放。

目标:选择一个技能和一个起始时间,使得在  时间内造成的总伤害最大。

输入描述

第一行:一个整数 ,表示总战斗时长 ()

第二行: 个整数,表示每个技能的伤害值  ()

第三行: 个整数表示每个技能的冷却时间  ()

其中,第二第三行的数据个数一定是相等的。

输出描述

总伤害最大值。

样例1

输入

10503

输出

200

说明技能只有 1 个,伤害 50,冷却时间 3。

  • 起始时间 :可在 0, 3, 6, 9 释放  4 次
  • 总伤害 
  • 其他起始时间不会更多
  • 输出 200

样例2

输入

5302

输出

90

说明

  • : 0, 2, 4  3 次 
  • : 1, 3  2 次 
  • 最大是 90

分析

难度:签到题

1)依次枚举每个技能能达到的最大伤害,取最大值即可

2)每个技能的最大伤害

参考c++代码

#include<iostream>#include<vector>#include<algorithm>#include<cmath>using namespace std;intmain(){int n;vector<longlong> damages;vector<int> cooldowns;longlong val;string line;// 读取伤害值行if (getline(cin, line)) {if (!line.empty()) {stringstreamss(line);while (ss >> val)                 damages.push_back(val);        }    }// 读取冷却时间行if (getline(cin, line)) {if (!line.empty()) {stringstreamss(line);int cd_val;while (ss >> cd_val)                 cooldowns.push_back(cd_val);        }    }longlong max_damage = 0;    n = damages.size();for (int i = 0; i < n; ++i) {longlong d = damages[i];int c = cooldowns[i];int gap = max(1, c);longlong count = 0;        count = (longlong)(T - 1) / gap + 1;longlong total_dmg = count * d;if (total_dmg > max_damage) {            max_damage = total_dmg;        }    }cout << max_damage << endl;return 0;}

第2题-多多的特殊三元组

题目内容

在多多王国的数字世界里,多多和我们遇到了一个有趣的数值问题。给定一个整数数组 ,需要找出所有满足特定条件的特殊三元组。

问题描述

特殊三元组定义为满足以下条件的三元组 :

,其中 

满足:

由于答案可能非常大,请返回结果对  取余数后的值。

输入描述

第一行:包含一个整数 ,表示测试用例的数量。每个测试用例的输入如下:

  • 第一行:一个整数 ,表示数组  的长度 ()
  • 第二行:包含  个整数,表示数组  的元素 ()

输出描述

对于每个测试用例,输出一个整数,表示满足条件的特殊三元组的数量(对  取模)。

样例1

输入

40 1 0 0

输出

0

样例2

输入

36 3 6

输出

1

样例3

输入

58 4 2 8 4

输出

2

这是一个经典的数组计数问题。我们需要在满足下标顺序  的前提下,统计满足特定数值关系的三元组数量。

解题思路

对于每一个固定的中间位置 ,我们需要知道:

  • 在它左边()有多少个数字等于 
  • 在它右边()有多少个数字等于 
  • 根据乘法原理,以当前  为中间点的特殊三元组数量 = **(左侧满足条件的个数)  (右侧满足条件的个数)**。

算法优化

  • 暴力法:对每个  遍历左右两边,时间复杂度为 。考虑到  高达 300,000,这会超时。
  • 优化法(后缀计数):我们可以先预处理出整个数组中每个数字出现的次数(作为初始的“右侧集合”)。然后从左向右遍历数组,动态维护“左侧集合”和“右侧集合”。
    • 当遍历到  时,先将  从右侧计数的哈希表中移除(因为它现在既不是左也不是右,而是中间点)。
    • 查询左侧哈希表中  的数量。
    • 查询右侧哈希表中  的数量。
    • 计算乘积并累加到答案中。
    • 最后将  加入左侧哈希表,供后续元素使用。
  • 使用哈希表 unordered_map,时间复杂度降为 ,空间复杂度 ,满足要求。

C++ 代码实现

#include<iostream>#include<vector>#include<unordered_map>using namespace std;constint MOD = 1e9 + 7;voidsolve(){int n;if (!(cin >> n)) return;vector<longlongnums(n);for (int i = 0; i < n; ++i) {cin >> nums[i];    }// right_count 存储当前位置右侧所有数字的出现次数unordered_map<longlonglonglong> right_count;for (longlong num : nums) {        right_count[num]++;    }// left_count 存储当前位置左侧所有数字的出现次数unordered_map<longlonglonglong> left_count;longlong total_ways = 0;for (int j = 0; j < n; ++j) {longlong val_j = nums[j];// 将当前元素从右侧集合中移除,因为它现在是中间点 j        right_count[val_j]--;// 目标值:nums[i] 和 nums[k] 必须等于 2 * nums[j]longlong target = 2 * val_j;// 获取左侧满足 nums[i] == target 的数量longlong left_cnt = 0;if (left_count.find(target) != left_count.end()) {            left_cnt = left_count[target];        }// 获取右侧满足 nums[k] == target 的数量longlong right_cnt = 0;if (right_count.find(target) != right_count.end()) {            right_cnt = right_count[target];        }// 累加方案数        total_ways = (total_ways + (left_cnt % MOD) * (right_cnt % MOD)) % MOD;//将当前元素加入左侧集合,供未来的 j 使用        left_count[val_j]++;    }cout << total_ways << endl;}intmain(){    ios_base::sync_with_stdio(false);cin.tie(NULL);int t;if (cin >> t) {while (t--) {            solve();        }    }return 0;}

第3题-多多爱学习

题目内容

多多是个爱学习的人,但不巧的是最近家里在装修,他只能去附近的自习室学习,自习室有很多房间,每个房间只有一段时间是空的(不必从最早空闲的自习室开始自习)。

为了不让其他人占用,多多必须在空闲开始时就到达,也不必等到空闲结束就能离开。

多多从不同的房间转移,至少需要 2 分钟时间,他想知道给定条件下,他最长能学习多长时间。

输入描述

第一行:输入一个数字,表示自习室的个数 

后面输入  行,每行两个数字 ,代表自习室第  分钟起空闲,空闲持续  分钟。( 都是大于 0 的正整数,

输出描述

小多最长能学习的分钟数 

样例1

输入

20 55 2

输出

5

说明小多在第一个房间学习 5 分钟

样例2

输入

20 50100 50

输出

100

说明小多在第一个房间学习 50 分钟,转移到第二个房间学习 50 分钟,一共可以学习 100 分钟。

问题分析

这是一个经典的线性DP问题。需要在多个时间段中进行选择,使得在满足转移时间约束的前提下,总的学习时间最长。

  1. 状态定义: 对于每一个自习室 ,定义  为:以自习室  作为最后(或当前)一个自习室时,多多能够获得的最大学习时间

  2. 状态转移: 对于当前的自习室 (空闲开始时间为 ,持续时间为 ),我们有两种选择:

    因此,状态转移方程为:

    如果没有任何之前的自习室  满足条件,那么  为 0。

    • 只在这个自习室学习:那么学习时间为 
    • 从之前的某个自习室  转移过来:必须满足转移时间至少为 2 分钟,即 。此时,总学习时间为 
  3. 优化: 由于  最大可达 ,如果采用双重循环枚举所有的  和 ,时间复杂度为 ,会超时。 我们需要将时间复杂度优化到 

    • 首先,将所有自习室按照空闲开始时间  从小到大排序
    • 排序后,对于当前的自习室 ,所有合法的  一定在  的前面。我们需要快速找到前面所有满足  的  中, 的最大值。
    • 我们可以使用二分查找来定位这个边界,并使用前缀最大值数组来在  时间内获取该边界之前的最大  值。

具体步骤

  1. 读入数据,将每个自习室表示为 (start, end),其中 start = Xend = X + Y
  2. 按照 start 对所有自习室进行升序排序。
  3. 初始化一个数组 max_dpmax_dp[i] 表示前  个自习室中最大的  值。
  4. 遍历排序后的自习室:
    • 当前自习室的开始时间为 cur_start
    • 我们需要在前面找到最大的 end_j <= cur_start - 2
    • 使用 std::upper_bound 在 ends 数组(记录每个自习室的结束时间)中查找 cur_start - 2 的位置。
    • 根据找到的位置,从 max_dp 数组中获取之前的最大学习时长 prev_max
    • 当前自习室的最大学习时长 dp[i] = prev_max + Y_i
    • 更新 max_dp[i] = max(max_dp[i-1], dp[i])
  5. 最终的答案就是 max_dp[N-1]

注:这个假设(即“按照自习室空闲开始时间 X 排序后,最优解的转移路径一定是从前往后”)能够成立,其核心在于时间的单向性(不可逆)。有兴趣的可以进一步分析。

C++ 代码实现

#include<iostream>#include<vector>#include<algorithm>using namespace std;intmain(){    ios_base::sync_with_stdio(false);cin.tie(nullptr);int N;if (!(cin >> N)) return0;vector<pair<longlonglonglong>> rooms(N);for (int i = 0; i < N; ++i) {longlong X, Y;cin >> X >> Y;        rooms[i] = {X, X + Y};     }// 按照开始时间排序    sort(rooms.begin(), rooms.end());vector<longlongends(N);// dp[i] 记录以第 i 个自习室结尾的最大学习时间vector<longlongdp(N);// max_dp[i] 记录前 i 个自习室中 dp 的最大值vector<longlongmax_dp(N);for (int i = 0; i < N; ++i) {longlong cur_start = rooms[i].first;longlong duration = rooms[i].second - rooms[i].first;        ends[i] = rooms[i].second;// 寻找前面结束时间 <= cur_start - 2 的自习室// upper_bound 找到第一个 > cur_start - 2 的位置,减 1 即为最后一个 <= cur_start - 2 的位置longlong target = cur_start - 2;auto it = upper_bound(ends.begin(), ends.begin() + i, target);longlong prev_max = 0;if (it != ends.begin()) {int idx = (it - ends.begin()) - 1;            prev_max = max_dp[idx];        }        dp[i] = prev_max + duration;// 更新前缀最大值if (i == 0) {            max_dp[i] = dp[i];        } else {            max_dp[i] = max(max_dp[i - 1], dp[i]);        }    }cout << max_dp[N - 1] << endl;return 0;}

第4题-多多的道路修建II

题目内容

多多现在在负责多多乡村的修建。

道路修建问题可以看作是在一条直线上,有  个单位。

经过认真分析,他发现每一段路有两种修建的方案:"修1"和"修2",分别为用 的石砖和用  的石砖进行铺设。

多多规定,一段有两种方案的路,可以把道路的空间给完全铺掉。

石块不能拆分(会影响美观),以及石块之间不可重叠(道路会凹凸不平)。

注: 也就是道路是一个长为 、宽为  的矩形,需要用  和  的砖铺满。

输入描述

共一行,两个正整数  和 ,分别表示要修建的道路的长度。 ()

输出描述

输出一个整数,表示不同修建方案的数量。由于答案可能会很大,只需要输出答案对  取模后的值即可。

补充说明

对于80%的数据,;对于100%的数据,有 

样例1

输入

4 2

输出

5

说明以字符  表示  的石砖,字符  表示  的石砖的 ,满足修建的方案有:

(1)OOOOOOOO(2)XXOOXXOO(3)OXXOOXXO(4)OOXXOOXX(5)XXXXXXXX

样例2

输入

3 3

输出

5

样例3

输入

2 5

输出

8

思路分析

这里道路是一个长为 、宽为  的矩形,需要用  和  的砖铺满。

因为  很小,而  很大,所以不能直接按  做普通动态规划。可以使用状态压缩动态规划 + 矩阵快速幂。

按列从左到右铺路。

用一个  位二进制状态 mask 表示当前列中哪些位置已经被上一列放出的  砖占用。

对于每一列,从当前状态 mask 出发,用深度优先搜索枚举当前列的所有铺法,得到下一列状态 nextMask。

转移方式有两种:

  • 如果当前位置已经被占用,跳过。
  • 如果当前位置没被占用:
    • 放一个  砖。
    • 如果当前位置和下一行都空,可以放一个  砖,它会占用下一列对应的两个格子,所以要在 nextMask 中标记。

这样可以得到一个大小为  的转移矩阵 

最终答案就是从初始状态 0 经过  列后回到状态 0 的方案数,即矩阵  中对应的值。

复杂度分析

设 

构造转移矩阵的复杂度为 

矩阵快速幂的时间复杂度为 

由于 ,所以 ,复杂度完全可以通过。

空间复杂度为 

参考代码

#include<iostream>#include<vector>#include<cstring>using namespace std;constint MOD = 1000000007;constint MAX_M = 5;constint MAX_STATE = 1 << MAX_M; // 32structMatrix {longlong mat[MAX_STATE][MAX_STATE];int size;    Matrix(int sz = 0) : size(sz) { memset(mat, 0sizeof(mat)); }};Matrix multiply(const Matrix& A, const Matrix& B){Matrix C(A.size);for (int i = 0; i < A.size; ++i) {for (int k = 0; k < A.size; ++k) {if (A.mat[i][k] == 0continue;for (int j = 0; j < A.size; ++j) {                C.mat[i][j] = (C.mat[i][j] + A.mat[i][k] * B.mat[k][j]) % MOD;            }        }    }return C;}Matrix power(Matrix base, longlongexp){Matrix result(base.size);for (int i = 0; i < base.size; ++i) result.mat[i][i] = 1;while (exp > 0) {if (exp & 1) result = multiply(result, base);        base = multiply(base, base);exp >>= 1;    }return result;}boolisValidRightHalf(int mask, int M){int i = 0;while (i < M) {if (mask & (1 << i)) {if (i + 1 >= M || !(mask & (1 << (i + 1)))) returnfalse;            i += 2;        } else {            i++;        }    }returntrue;}intmain(){longlong N;int M;if (!(cin >> N >> M)) return0;int STATE = 1 << M;Matrix T(STATE);for (int prev = 0; prev < STATE; ++prev) {int need = (~prev) & (STATE - 1);for (int cur = 0; cur < STATE; ++cur) {if ((cur & ~need) != 0continue;if (!isValidRightHalf(cur, M)) continue;            T.mat[prev][cur] = 1;        }    }    Matrix TN = power(T, N);cout << TN.mat[0][0] << endl;return 0;}