百度后端笔试 2023-04-10
T1 小红的子数组拆分
题意
小红拿到了一个长度为n的数组,她希望把该数组拆分成k个非空子序列(每个元素必须出现在某个子序列中,且恰好出现一次),使得这k个子序列的平均数之和尽可能小。你能帮帮她吗?
注,子序列可以不连续。例如数组为[3,2,1,3],k=2时,子序列可以拆分为[3,1]和[2,31]。
1 <= k, n <= 1e5, -10^9 <= ai <= 10^9
思路
对于一个数字 x,假设分配到了长度为 y 的数组中,那么它对最终答案的贡献是 x / y。
对 x 分正负讨论:
- 正数,使它尽可能的分配到更长的子数组中。
- 负数,使它尽可能的分配到长度为 1 的子数组中。
也就是说数字越小,分配到的子数组长度应该尽可能的小,于是有了以下做法:
对数组排序,然后前 k - 1 个数字单独分组,最后 n - k + 1 个 数字分成一组。
时间复杂度 O(nlogn)。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
int n, k;
cin >> n >> k;
vector<ll> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end());
ll res = 0;
for (int i = 0; i < k - 1; i++) {
res += a[i];
}
ll sum = 0;
for (int i = k - 1; i < n; i++) {
sum += a[i];
}
double ans = res + 1.0 * sum / (n - k + 1);
printf("%.10f\n", ans);
}
T2 red 回文串
题意
给定一个整数x,请你构造一个仅由'r'、‘e'、’d’三种字符组成的字符串,其中回文子串的数量恰好为x。字符串的长度不得超过10^5。
1 <= x <= 10^9
思路
这个题目老朋友了,由相同字符构成的长度为 n 的字符串共有 (1 + n) * n / 2 个回文串,我们先找到最大的 n 使得 (1 + n) * n / 2 <= x,用 n 个 r 去填充,然后剩下按照 "red" 去补齐即可。
时间复杂度 O(sqrt(n))。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
ll x;
cin >> x;
ll n = 1, sum = 0;
string s = "";
while (sum + n <= x) {
sum += n;
s += "r";
n++;
}
int c = 0;
for (int i = sum + 1; i <= x; i++) {
if (c == 0) {
s += 'e';
} else if (c == 1) {
s += 'd';
} else {
s += 'r';
}
c = (c + 1) % 3;
}
cout << s << endl;
return 0;
}
T3 小红的子树操作
题意
小红拿到了一棵有根树,i 号节点的权值为 ai 。已知1号节点为根节点。
小红有q次操作,每次操作会选择一个节点x,使得x为根的子树上,所有节点的权值乘以y。
小红想知道,在q次操作结束以后,对于节点 i,以节点 i 为根的子树的所有点权值乘积末尾有多少个0?
1 <= n, q <= 10^5
1 <= ai, y <= 10^9
思路
这个题目也是老朋友了。
首先,乘积末尾 0 的个数,只跟乘数分解质因数后 2 和 5 的指数有关,这两个指数的最小值即为末尾 0 的个数。
然后,对以 x 为根的子树操作,只需要先给 x 打上标记,q 次操作全部打上标记后,dfs 的过程中将标记传递到子树上的每个结点,累计求和。
最后回溯的时候,再求一次和,表示子树所有数的乘积。
时间复杂度 O(n+qlogy)。
// 小红的子树操作
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
int n, q;
cin >> n;
vector<ll> a(n), c2(n), c5(n);
vector<vector<int>> g(n);
// 计算 x 分解质因数后 base 的指数
auto get = [&](ll x, int base) {
ll cnt = 0;
while (x % base == 0) {
cnt++;
x /= base;
}
return cnt;
};
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n - 1; i++) {
int x, y; cin >> x >> y;
x--; y--;
g[x].push_back(y);
g[y].push_back(x);
}
cin >> q;
for (int i = 0; i < q; i++) {
int x, y;
cin >> x >> y;
c2[x - 1] += get(y, 2);
c5[x - 1] += get(y, 5);
}
function<void(int, int)> dfs = [&](int x, int f) {
// 递归过程中,给 x 的子节点 y 累加标记
for (auto &y : g[x]) {
if (y == f) continue;
c2[y] += c2[x];
c5[y] += c5[x];
dfs(y, x);
}
// 回溯过程中,求以 x 为根的子树中所有节点的标记之和。
for (auto &y : g[x]) {
if (y == f) continue;
c2[x] += c2[y];
c5[x] += c5[y];
}
// 这里对于 x 原本的权值要单独算,这一部分不能递归下去
c2[x] += get(a[x], 2);
c5[x] += get(a[x], 5);
};
dfs(0, -1);
for (int i = 0; i < n; i++) {
cout << min(c2[i], c5[i]) << " \n"[i == n - 1];
}
return 0;
}
#软件开发2023笔面经##百度##笔试#记录2023年-2024年的笔试、面试问题~