2023大厂笔试模拟练习网站(含题解)
www.codefun2000.com
最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据,挂载到我们的OJ上,供大家学习交流,体会笔试难度。现已录入200+道互联网大厂模拟练习题,还在极速更新中。欢迎关注公众号“塔子哥学算法”获取最新消息。
提交链接:
https://codefun2000.com/p/P1140
为了更好的阅读体检,可以查看OJ上的题解。进入提交链接,点击右边菜单栏的"查看塔子哥的题解"
题目内容
塔子哥是一个年轻的魔法学徒,他一直在努力学习各种魔法技能。他听说倒水魔法是一种非常强大的魔法,但也是一种非常难掌握的魔法。他早早地来到了魔法训练室,希望能够掌握这种魔法。
魔法训练室里有 n n n 个神奇的杯子,有着不同的大,假设第 i i i 个杯子已满,向其倒水,多余的水会正正好好流向第 i + 1 i+1 i+1 个杯子(如果 i = n i=n i=n 时没有下一个杯子,不会有杯子接住此时多余的水而溢出到魔法训练室的水池)。
这些杯子有着初始固定的水量,每次练习后杯子里的水都会还原到最初状态。每次练习时,魔法黑板会告诉塔子哥需要将哪一人杯子倒满水。因为每个杯子的材质和形状有所不同所以对其释放倒水魔法需要消耗的魔法值不同。塔子哥想尽可能多练习,所以需要最小化每次消耗的魔法值的总量。
输入描述
第一行一个整数 n n n ,表示杯了数量。
第二行 n n n 个整数 x 1 , x 2 , . . . , x n x_1,x_2,...,x_n x1,x2,...,xn ,依次表示第 i i i 个杯子能容纳水的量(单位为毫升)。
第三行 n n n 个整数 y 1 , y 2 , . . . , y n y_1,y_2,...,y_n y1,y2,...,yn ,依次表示第 i i i 个杯子初始有的水量(单位为毫升)。
第四行 n n n 个整数 z 1 , z 2 , . . . , z n z_1,z_2,...,z_n z1,z2,...,zn ,依次表示对第 i i i 个杯子每添加1毫升水需要消耗的法力值。
第五行一个整数 m m m ,表示练习的数量。
第六行 m m m 个整数 q 1 , q 2 , … , q n q_1,q_2,…,q_n q1,q2,…,qn ,依次表示第 i i i 次练习时需要将第 q i q_i qi 个杯子倒满。(每次练习过后,杯子里的水量都会还原为初始状态,不会影响到下一次练习)
1 ≤ n , m ≤ 3000 , 1 ≤ y i ≤ x x ≤ 1 0 9 , 1 ≤ z i ≤ 300 , 1 ≤ q i ≤ n 1\le n,m\le 3000,1\le y_i\le x_x \le 10^9,1\le z_i \le 300,1\le q_i\le n 1≤n,m≤3000,1≤yi≤xx≤109,1≤zi≤300,1≤qi≤n
输出描述
输出第一行 m m m 个数,依次表示每次训练时需要消耗的最小法力值。
如果杯子初始本身就是满的,则需要消耗的法力值为 0 0 0 。
样例
输入
3
1 2 3
1 1 2
1 2 5
2
3 1
输出
2 0
样例解释
第一次训练,最优方案如下:
初始时杯子的水量和最大容量分别为
1/1 1/2 2/3
- 给1号杯子(本身已满)倒水1毫升,消耗1点法力,水溢出转移到2号杯子,当前状态为1/1 2/2 2/3
- 2.继续给1号杯子(本身已满)倒水1毫升,消耗1点法力,水溢出到2号杯子(也已满),继续溢出到3号杯子,此时3号杯子也被成功注满水,状态为:
1/1 2/2 3/3
共消耗2点法力。可以证明没有更优方案。
第二次训练时,
初始时杯子的水量和最大容量分别为(注意不同训练互不影响,因为训练结束后魔法会让水杯还原为初始状态)
1/1 1/2 2/3
可以发现1号杯子已满,不用注水,消耗法力为0。
思路:贪心+前缀和
对于第 q i q_i qi 次询问,目标是将第 j j j 个杯子装满,那么选择从第 k ( k ≤ j ) k (k\leq j) k(k≤j) 个杯子开始倒水,那么 [ k , j ] [k, j] [k,j] 这些杯子被装满的水均来自第 k k k 个杯子溢出的。
选择从第
k
(
k
≤
j
)
k (k\leq j)
k(k≤j) 个杯子开始倒水,则需要使得
[
k
+
1
,
j
]
[k+1,j]
[k+1,j] 这些杯子都倒满,魔力值为
z
k
×
∑
i
=
k
j
(
x
i
−
y
i
)
z_k\times \sum\limits_{i=k}^j (x_i-y_i)
zk×i=k∑j(xi−yi)。答案即
min
t
=
1
j
(
z
k
×
∑
i
=
k
j
(
x
i
−
y
i
)
)
\min\limits_{t=1}^j(z_k\times \sum\limits_{i=k}^j (x_i-y_i))
t=1minj(zk×i=k∑j(xi−yi))
此外,代码中使用了一个 trick,就是对于每个查询,从
j
j
j 到
1
1
1 来枚举从每个杯子开始倒水(而不是从
1
1
1到
j
j
j),这样我们就可以维护这其中需要添加的水量。因为如果是从
1
1
1 到
j
j
j 来枚举,则对于每次的倒水量都需要
O
(
n
)
O(n)
O(n) 去查询,导致总复杂度是
O
(
n
3
)
O(n^3)
O(n3) , 当然也可以直接使用前缀和来维护。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
最优结构
选择从第 k k k 个杯子装满水,必然有 z k < min ( z k + 1 z k + 2 , ⋯ , z j ) z_k < \min{(z_{k+1}z_{k+2},\cdots,z_j)} zk<min(zk+1zk+2,⋯,zj)。
否则选择一个 i n d e x ∈ [ k + 1 , j ] index\in [k+1,j] index∈[k+1,j],有 z i n d e x ≤ z k z_{index}\leq z_k zindex≤zk,同时有 ∑ i = i n d e x j ( x i − y i ) ≤ ∑ i = k j ( y i − x i ) \sum_{i=index}^j(x_i-y_i)\leq \sum_{i=k}^j(y_i-x_i) ∑i=indexj(xi−yi)≤∑i=kj(yi−xi),从第 k k k 个杯子开始倒水花费的魔力值必然不会小于从第 i n d e x index index 个杯子倒水花费的魔力值。
类似知识点题目推荐
这道题的特点是枚举得到最优解 , 可以考虑以下几个题
leetcode
1.剑指 Offer II 013. 二维子矩阵的和 --前缀和开胃菜
2.剑指 Offer II 040. 矩阵中最大的矩形
CodeFun2000
1.P1175 华为od 2023.04.08–第二题-新学校选址
2.P1214 民生科技 2023.04.22-技术研发春招-第一题-删点
3.P1188 2023.04.12-微众银行春招-第三题-魔法收纳器 --难度较高!
代码
C++
#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
int x[N], y[N], z[N];
int n, m;
int main()
{
// 读入
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &x[i]);
for (int i = 1; i <= n; ++i) scanf("%d", &y[i]);
for (int i = 1; i <= n; ++i) scanf("%d", &z[i]);
scanf("%d", &m);
// 处理每个询问
for (int i = 1; i <= m; ++i) {
int p; scanf("%d", &p);
// 根据思路给出的公式计算
long long ans = 1e18;
long long sum = 0;
for (int j = p; j >= 1; --j) {
sum += (x[j] - y[j]);
ans = min(ans, sum * z[j]);
}
printf("%lld%c", ans, " \n"[i == m]);
}
return 0;
}
python
# 读入
n = int(input())
x = list(map(int, input().split()))
y = list(map(int, input().split()))
z = list(map(int, input().split()))
m = int(input())
q = list(map(int, input().split(" ")))
# 根据思路给出的公式计算即可
for i in range(m):
ans = 10**18
s = 0
for j in range(q[i]-1, -1, -1):
s += (x[j] - y[j])
ans = min(ans, s * z[j])
print(ans, end="\n" if i == m - 1 else " ")
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读入
int n = scanner.nextInt();
int[] x = new int[n+1];
int[] y = new int[n+1];
int[] z = new int[n+1];
for (int i = 1; i <= n; ++i) x[i] = scanner.nextInt();
for (int i = 1; i <= n; ++i) y[i] = scanner.nextInt();
for (int i = 1; i <= n; ++i) z[i] = scanner.nextInt();
// 处理每个询问
int m = scanner.nextInt();
for (int i = 0; i < m; ++i) {
int p = scanner.nextInt();
long ans = (long)1e18;
long sum = 0;
// 根据思路给出的公式计算
for (int j = p; j >= 1; --j) {
sum += (x[j] - y[j]);
ans = Math.min(ans, sum * z[j]);
}
System.out.print(ans + (i == m-1 ? "\n" : " "));
}
}
}
Go
package main
import (
"fmt"
)
func main() {
var n, m int
// 读入
fmt.Scan(&n)
x := make([]int, n)
y := make([]int, n)
z := make([]int, n)
for i := 0; i < n; i++ {
fmt.Scan(&x[i])
}
for i := 0; i < n; i++ {
fmt.Scan(&y[i])
x[i] -= y[i]
}
for i := 0; i < n; i++ {
fmt.Scan(&z[i])
}
fmt.Scan(&m)
q := make([]int, m)
for i := 0; i < m; i++ {
fmt.Scan(&q[i])
q[i]--
}
// 处理每一个询问
for i := 0; i < m; i++ {
var ans int = 1e18
var s int
// 倒序处理
for j := q[i]; j >= 0; j-- {
s += x[j]
if ans > s*z[j] {
ans = s * z[j]
}
}
fmt.Printf("%d", ans)
if i == m-1 {
fmt.Println("")
} else {
fmt.Print(" ")
}
}
}
Js
process.stdin.resume();
process.stdin.setEncoding("utf-8");
let input = "";
process.stdin.on("data", (data) => {
input += data;
});
process.stdin.on("end", () => {
// 读入
const lines = input.trim().split("\n");
const n = parseInt(lines[0]);
const x = lines[1].trim().split(" ").map((num) => parseInt(num));
const y = lines[2].trim().split(" ").map((num) => parseInt(num));
const z = lines[3].trim().split(" ").map((num) => parseInt(num));
const m = parseInt(lines[4]);
const q = lines[5].trim().split(" ").map((num) => parseInt(num));
// 处理每个询问
for (let i = 0; i < m; i++) {
let ans = 10 ** 18;
let s = 0;
// 根据题目公式来做
for (let j = q[i] - 1; j >= 0; j--) {
s += x[j] - y[j];
ans = Math.min(ans, s * z[j]);
}
process.stdout.write(ans + (i === m - 1 ? "\n" : " "));
}
});