2025秋招 | pdd 0825 笔试记录
Q1
题目大意
给定一个有n个节点的树,树中的每条边都有一个权值,同时给定一个长度为 n
的整数数组 ,删除树中的若干条边后(也可以不删),整棵树就会变成一个有 x
个连通块的图,我们定义这个图的得分为:剩余边的权重之和+ ,求可能获得的最大分值是多少
示例1: 1 3 1 3 4 1 2 1 2 3 2 输出: 5 示例2: 2 3 3 3 4 1 2 1 2 3 2 3 1 2 5 1 2 1 2 3 2 输出: 6 5
解题思路
容易知道,整棵树一开始就是一个连通块,我们每删除一条边,就可以多得到一个连通块。因此我们可以考虑枚举删除边的个数:i
,当我们删除了图中的 i
条边后,图中一定会有 1+i
个连通块,而为了得到尽可能大的分治,我们应该去尽量删除前 i
个权重小的边。枚举 i:0~n-1
,记录其中的最大权值即可。
代码实现——贪心
#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; using LL = long long; int maxValue(vector<int> &arr, vector<int> &edgeWight) { int n = arr.size(); sort(edgeWight.begin(), edgeWight.end()); // 将边按照权值从小到大的顺序排列 edgeWight.insert(edgeWight.begin(), 0); // 为了方便计算前缀和,这里额外插入一个0 for (int i = 1; i <= n - 1; i++) { edgeWight[i] += edgeWight[i - 1]; } int res = 0; for (int i = 0; i <= n - 1; i++) { // 当我们删除 i 条边时,由于我们只删前i条权值最小的边 // 因此剩余边的权值之和为 edgeWight[n-1]-edgeWight[i] res = max(res, arr[i] + edgeWight[n - 1] - edgeWight[i]); } return res; } int main() { int T; cin >> T; while (T--) { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } vector<int> edgeWight(n - 1); for (int i = 0; i < n - 1; i++) { int u, v, w; cin >> u >> v >> w; edgeWight[i] = w; } cout << maxValue(a, edgeWight) << endl; } return 0; }
时间复杂度:
Q2
题目大意
给定一个正整数数组,我们可以执行两种操作:
- 选中一个偶数,并将其值减半
- 选中两个数,移除这两个数,并向数组中新增这两个数字之和
求把数组变为全是奇数时的最少操作次数
示例: 3 3 2 4 4 2 1 9 5 1 2 3 4 5 输出: 3 0 2
解题思路——贪心
由数学知识可知 奇数+偶数 = 奇数
,因此我们可以考虑先对数组中的元素按照奇偶性进行划分
只要数组中存在奇数,那么我们可以连续采用操作2,将这个奇数依次和偶数进行相加,最后一定可以得到一个全为奇数的数组
而如果数组中的元素全部为偶数,我们依然可以考虑沿用这一策略——我们先考虑使用操作1,把其中一个偶数变成奇数,然后再不断执行操作2,消去所有的偶数。而为了得到最少的操作次数,我们可以考虑遍历所有的偶数,计算其变成奇数所需的操作1的次数。
代码实现
#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; using LL = long long; int minOpCount(vector<LL> arr) { int n = arr.size(); int oddCnt = 0, evenCnt = 0; int minEvenOpCount = INF; // 把某个偶数通过/2操作变成奇数的最少次数 for (LL x : arr) { if (x & 1) { oddCnt++; } else { evenCnt++; // 找到x的最低位的1 int cnt = 0; while (!((x >> cnt) & 1)) { cnt++; } minEvenOpCount = min(minEvenOpCount, cnt); } } if (oddCnt > 0) { return evenCnt; } return evenCnt - 1 + minEvenOpCount; } int main() { int T; cin >> T; while (T--) { int n; cin >> n; vector<LL> arr(n); for (int i = 0; i < n; i++) { cin >> arr[i]; } cout << minOpCount(arr) << endl; } return 0; }
时间复杂度:
Q3
题目大意
给定一个正整数数组 arr[]
和一个正整数 x
,每次你可以用 x
和数组中一个比 x
小的元素进行交换,求经过最少多少次交换后,可以使得数组 arr[]
是单调不减的
示例: 5 5 2 1 3 2 4 输出: 3
解题思路——记忆化搜索
从后向前考虑,假设当前考虑 nums[i]
:
- 若
nums[i]<nums[i-1]
: 若 x >= nums[i-1],此时我们可以把 nums[i] 和 x 进行交换,这样就能保证第 i 号位和第 i-1 号位之间的关系是符合要求的,我们接下来只要去求出从 i-1 开始考虑,且 x=nums[i] 的最少交换次数即可若 x<nums[i-1],则交换了也没用,此时一定无法使得数组单调不减 - 若
nums[i] >= nums[i-1]
: 若 x<=nums[i],此时无法交换,因此我们接下来只要去求出从 i-1 开始考虑,且 x = x 的最少交换次数即可若 x > nums[i]:如果我们不交换,那么后续就有可能把 x 和 nums[0:i-1] 之中的某个数进行交换,这样就会使得 arr[] 不满足单调非递减的条件,因此,我们仍要进行分类讨论: 若 nums[0:i] 已经是非递减的情况,那么我们确实可以不用交换,且此时的最少交换次数是0否则,我们一定要把 x 和 nums[i] 进行交换(不然后面就会破坏非递减性),此我们接下来只要去求出从 i-1 开始考虑,且 x = nums[i] 的最少交换次数即可
不难看出,上述问题可以用递归的思路解决,我们可以设置递归函数 dfs(i,x)
来求解用 x
替换,将 nums[0:i]
变成非递减时所需的最少操作次数。为了减少算法的时间开销,这里我们引入 vector<unordered_map<int, int>> dp
来缓存 dfs(i,x)
的结果
代码实现
#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; using LL = long long; int minChangeCnt(vector<int> &arr, int x) { int n = arr.size(); vector<bool> isSorted(n, false); // 判断arr[0:i]是否已非递减 isSorted[0] = true; for (int i = 1; i < n; i++) { if (arr[i] >= arr[i - 1]) { isSorted[i] = true; } else { break; } } vector<unordered_map<int, int>> dp(n, unordered_map<int, int>()); function<int(int, int)> dfs = [&](int i, int x) -> int { if (i == 0 || isSorted[i]) { return 0; } if (dp[i].count(x)) { return dp[i][x]; } int res = INF; if (arr[i] < arr[i - 1]) { if (x >= arr[i - 1]) { res = min(res, 1 + dfs(i - 1, arr[i])); } } else { if (x <= arr[i]) { res = min(res, dfs(i - 1, x)); } else { res = min(res, 1 + dfs(i - 1, arr[i])); } } return dp[i][x] = res; }; int res = dfs(n - 1, x); if (res >= INF) res = -1; return res; } int main() { int n, x; cin >> n >> x; vector<int> arr(n); for (int i = 0; i < n; i++) { cin >> arr[i]; } cout << minChangeCnt(arr, x) << endl; return 0; }
补充
本解法好像只能过 60%,希望有大佬看看为什么错
Q4
题目大意
给定长度为 n
的 01
串,定义一次操作为,将整个字符串按顺序分为两部分,将两部分各自翻转后再按原顺序拼接。
问进行任意次的操作后,可以得到的最长的连续的交替的子串有多长。
示例: 5 10010 输出: 5 解释: 可以将10010 拆分为10|010 -> 01010 -> 交替子串的最大长度为5
解题思路——贪心
如果我们把 01
串看成一个首尾相连的串:
1 0 0 1 0 tip:我们把s,从s[0]开始,按照顺时针旋转的方向进行排列
对于 10|010 -> 01|010
的拆分,反应在环上就是:
1 0 0 \ 1 0
我们从断裂的地方(0)开始逆时针读取,就是操作后得到的数组。
也就是说,不论我们怎么操作,最后得到的字符串,实际上就是环上以不同起点,不同方向读取到的一个字符串
因此我们可以枚举起点+方向,计算每个情况的最大交替子串长度
进一步观察可以发现,我们以 s[1]
为起点,逆时针旋转得到的结果和以 s[2]
为起点,顺时针旋转得到的结果是一致的,因此这里我们实际上只要去计算所有以 nums[i]
为起点,顺时针旋转读取到的字符串中最长交替子串的长度即可
代码实现
#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; using LL = long long; int main() { int n; string s; cin >> n >> s; int res = 1; // 为了方便计算,我们将字符串复制一遍 s += s; // 枚举以nums[i]为起点,顺时针读取得到的字符串中最长的交替子串长度 for (int i = 0; i < n; i++) { int len = 1; for (int k = 1; k < n; k++) { if (s[i + k] != s[i + k - 1]) { len++; } else { res = max(res, len); len = 1; } } } // 枚举以nums[i]为起点,逆时针读取得到的字符串中最长的交替子串长度 for (int i = n; i < 2 * n; i++) { int len = 1; for (int k = 1; k < n; k++) { if(s[i - k] != s[i - k + 1]) { len++; } else { res = max(res, len); len = 1; } } } cout << res << endl; return 0; }
改进
实际上,对于最长交替子串,不论我们是通过顺时针旋转得到的,还是逆时针旋转得到的,其都不会改变交替性,因此这里实际上可以直接去计算 s+s
中的最长交替子串
#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; using LL = long long; int main() { int n; string s; cin >> n >> s; int res = 1; s += s; int len = 1; for (int i = 1; i < 2 * n; i++) { if (s[i] != s[i - 1]) { len++; } else { res = max(res, len); len = 1; } } cout << min(res, n) << endl; // 交替子串的长度不会大于n return 0; }
时间复杂度:
#秋招##秋招笔试##pdd##拼多多#