题目列表
3492. 船上可以装载的最大集装箱数量
3493. 属性图
3494. 酿造药水需要的最少总时间
3495. 使数组元素都变为零的最少操作次数
一、船上可以装载的最大集装箱数量
简单的数学题,直接照着题目算就行,代码如下
// C++
class Solution {
public:
int maxContainers(int n, int w, int maxWeight) {
return min(maxWeight/w, n*n);
}
};
# Python
class Solution:
def maxContainers(self, n: int, w: int, maxWeight: int) -> int:
return min(maxWeight // w, n * n)
二、属性图
判断连通分量的数目,可以用
d
f
s
dfs
dfs 或者并查集来做,关键是如何判断数组
a
a
a 和
b
b
b 中共有的不同整数的数量。我们可以用
s
e
t
set
set 分别统计数组
a
a
a 和
b
b
b 中的不同元素,然后求交集即可,代码如下
// C++
// dfs 写法
class Solution {
public:
int numberOfComponents(vector<vector<int>>& properties, int k) {
int n = properties.size(), m = properties[0].size();
vector<vector<int>> g(n);
vector<unordered_set<int>> v(n);
for(int i = 0; i < n; i++) {
v[i] = unordered_set<int>(properties[i].begin(), properties[i].end());
}
auto check = [&](int i, int j)->bool{
int cnt = 0;
for(const int& x : v[i]){
if(v[j].find(x) != v[j].end()){
cnt++;
}
}
return cnt >= k;
};
for(int i = 0; i < n; i++){
for(int j = i + 1; j < n; j++){
if(check(i, j)){
g[i].push_back(j);
g[j].push_back(i);
}
}
}
int cnt = 0;
vector<bool> vis(n);
auto dfs = [&](this auto && dfs, int x)->void{
vis[x] = true;
for(int y: g[x]){
if(!vis[y]){
dfs(y);
}
}
};
for(int i = 0; i < n; i++){
if(!vis[i]){
cnt++;
dfs(i);
}
}
return cnt;
}
};
// 并查集写法
class UnionFind{
vector<int> fa;
int cnt;
public:
UnionFind(int n) : fa(n, -1), cnt(n){}
void merge(int x, int y){
int fa_x = find(x), fa_y = find(y);
if(fa_x != fa_y){
fa[fa_x] = fa_y;
cnt--;
}
}
int find(int x){
int pa = x;
while(fa[pa] != -1){
pa = fa[pa];
}
while(x != pa){
int tmp = fa[x];
fa[x] = pa;
x = tmp;
}
return pa;
}
int count(){
return cnt;
}
};
class Solution {
public:
int numberOfComponents(vector<vector<int>>& properties, int k) {
int n = properties.size(), m = properties[0].size();
vector<vector<int>> g(n);
vector<unordered_set<int>> v(n);
for(int i = 0; i < n; i++) {
v[i] = unordered_set<int>(properties[i].begin(), properties[i].end());
}
auto check = [&](int i, int j)->bool{
int cnt = 0;
for(const int& x : v[i]){
if(v[j].find(x) != v[j].end()){
cnt++;
}
}
return cnt >= k;
};
UnionFind u(n);
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(check(i,j)) u.merge(i, j);
}
}
return u.count();
}
};
# Python
class UnionFind:
def __init__(self, n: int):
self.fa = [-1] * n
self.cnt = n
def find(self, x: int)->int:
if self.fa[x] == -1:
return x
self.fa[x] = self.find(self.fa[x])
return self.fa[x]
def merge(self, x: int, y: int):
fa_x = self.find(x)
fa_y = self.find(y)
if fa_x != fa_y:
self.fa[fa_x] = fa_y
self.cnt -= 1
def count(self):
return self.cnt
class Solution:
def numberOfComponents(self, properties: List[List[int]], k: int) -> int:
n = len(properties)
un = UnionFind(n)
sets = list(map(set, properties))
for i in range(n):
for j in range(i+1, n):
if len(sets[i] & sets[j]) >= k:
un.merge(i, j)
return un.count()
三、酿造药水需要的最少总时间
题目要求每个巫师按照顺序处理药水,并且每个药水在制作的过程中不能有停顿,也就是说,药水的制作过程必须是连续的,不能出现前面的巫师处理完药水之后,后面的巫师还在处理上一个药水的情况。
根据题意,我们有以下性质:
1、对于任意一个药水,一旦我们确定了它的开始制作时间,那么所有巫师的处理结束时间就确定了
2、对于任意一个巫师,他开始处理下一瓶药水的时间必须大于等于他处理完上一个药水的时间
由此,我们可以做出如下推导
- 设第 i i i 个药水的开始制作时间为 s t a r t i start_{i} starti,第 i + 1 i+1 i+1 个药水的开始制作时间为 s t a r t i + 1 start_{i+1} starti+1
- 则第 j j j 个巫师处理完第 i i i 个药水的时间为 s t a r t i + p r e j × m a n a i start_i+pre_{j}\times mana_i starti+prej×manai,第 j j j 个巫师开始处理第 i + 1 i+1 i+1 个药水的时间为 s t a r t i + 1 + p r e j − 1 × m a n a i + 1 start_{i+1}+pre_{j-1}\times mana_{i+1} starti+1+prej−1×manai+1,其中 p r e pre pre 表示 s k i l l skill skill 数组的前缀和
-
s
t
a
r
t
i
+
1
+
p
r
e
j
−
1
×
m
a
n
a
i
+
1
≥
s
t
a
r
t
i
+
p
r
e
j
×
m
a
n
a
i
start_{i+1}+pre_{j-1}\times mana_{i+1}\ge start_i+pre_{j}\times mana_i
starti+1+prej−1×manai+1≥starti+prej×manai
= > s t a r t i + 1 − s t a r t i ≥ p r e j × m a n a i − p r e j − 1 × m a n a i + 1 =>\ start_{i+1}-start_{i}\ge pre_{j}\times mana_i-pre_{j-1}\times mana_{i+1} => starti+1−starti≥prej×manai−prej−1×manai+1
= > s t a r t i + 1 − s t a r t i ≥ p r e j − 1 × ( m a n a i − m a n a i + 1 ) + s k i l l j × m a n a i =>\ start_{i+1}-start_{i}\ge pre_{j-1}\times (mana_i-mana_{i+1})+skill_{j}\times mana_{i} => starti+1−starti≥prej−1×(manai−manai+1)+skillj×manai
故每个药水的开始处理时间的间隔是 m a x ( p r e j − 1 × ( m a n a i − m a n a i + 1 ) + s k i l l j × m a n a i ) max(pre_{j-1}\times (mana_i-mana_{i+1})+skill_{j}\times mana_{i}) max(prej−1×(manai−manai+1)+skillj×manai),而第一个药水的开始时间为 0 0 0,由此我们可以算出最后一瓶药水的开始制作时间,从而计算出答案,代码如下
class Solution {
public:
long long minTime(vector<int>& skill, vector<int>& mana) {
int n = mana.size(), m = skill.size();
long long start = 0;
for(int i = 0; i < n - 1; i++){
long long pre = 0;
long long add = 0;
for(int j = 0; j < m; j++){
add = max(add, pre * (mana[i] - mana[i+1]) + 1LL * skill[j] * mana[i]);
pre += skill[j];
}
start += add;
}
long long pre = accumulate(skill.begin(), skill.end(), 0LL);
return start + pre * mana.back();
}
};
# Python
class Solution:
def minTime(self, skill: List[int], mana: List[int]) -> int:
n = len(mana)
start = 0
for i in range(n - 1):
pre = add = 0
for x in skill:
add = max(add, pre*(mana[i] - mana[i+1]) + x * mana[i])
pre += x
start += add
return start + sum(skill) * mana[-1]
四、使数组元素都变为零的最少操作次数
思路分析:
-
f l o o r ( x / 4 ) floor(x/4) floor(x/4) 等价于将 x x x 的二进制向右移动两位,所以一个数字最多被操作 16 16 16 次(数据范围最大为 1 0 9 10^9 109)
-
由于区间 [ l , r ] [l,r] [l,r] 中的数字是连续的,所以数字被操作的次数也应该是连续的,即类似 [ 1 , . . . , 1 , 2 , . . . , 2 , 3 , . . . , 3 ] [1,...,1,2,...,2,3,...,3] [1,...,1,2,...,2,3,...,3]
-
求最少操作次数,可以先算出每个数字需要的操作次数,组成一个数组 a a a,然后问题等价为每次从数组 a a a 中取两个数减去一,看至少需要多少次操作才能让数组 a a a 中的元素全为 0 0 0
-
由于最终剩下的数字每次减一都算一次操作,根据贪心,我们每次操作都让最大的两个数减一,让最后剩余的数字的个数尽可能的少
这个贪心有个结论:只有当 m a x ( a ) ∗ 2 > s u m ( a ) max(a)*2>sum(a) max(a)∗2>sum(a) 时,才会出现两个数重复匹配的情况 -
由于本题的数组 a a a 的数字是连续的且个数 ≥ 2 \ge2 ≥2,所以不可能出现 m a x ( a ) ∗ 2 > s u m ( a ) max(a)*2>sum(a) max(a)∗2>sum(a) 的情况,所以只要知道总操作次数 s s s,就能得出最小操作次数 c e i l ( s / 2 ) ceil(s/2) ceil(s/2)
-
如何快速求出区间内所有数字的操作次数?
- 根据 f l o o r ( x / 4 ) floor(x/4) floor(x/4) 等价于将 x x x 的二进制向右移动两位,得出
- [ 4 0 , 4 1 − 1 ] [4^0,4^1-1] [40,41−1] 中的数字需要 1 1 1 次操作变为 0 0 0
- [ 4 1 , 4 2 − 1 ] [4^1,4^2-1] [41,42−1] 中的数字需要 2 2 2 次操作变为 0 0 0
- …
- [ 4 k − 1 , 4 k − 1 ] [4^{k-1},4^k-1] [4k−1,4k−1] 中的数字需要 k k k 次操作变为 0 0 0
- 我们只要将 [ l , r ] [l,r] [l,r] 中的数字进行区间划分,然后分别统计即可
-
代码如下
// C++
class Solution {
public:
long long minOperations(vector<vector<int>>& queries) {
auto f = [](long long l, long long r)->long long{
long long res = 0, left = 1, right = 3;
int k = 1;
while(left <= r){
long long cur_l = max(left, l);
long long cur_r = min(right, r);
if(cur_l <= cur_r){
res += k * (cur_r - cur_l + 1);
}
left = left * 4; // 最多执行 16 次
right = left * 4 - 1;
k++;
}
return res;
};
long long ans = 0;
for(auto& q : queries){
ans += (f(q[0], q[1]) + 1)/2;
}
return ans;
}
};
# Python
class Solution:
def minOperations(self, queries: List[List[int]]) -> int:
def f(l: int, r: int)->int:
sum_k = 0
k = 1
left = 1
right = 3
while left <= r:
cur_l = max(l, left)
cur_r = min(r, right)
if cur_l <= cur_r:
sum_k += k * (cur_r - cur_l + 1)
left = left * 4
right = left * 4 - 1
k += 1
return sum_k
total = 0
for l, r in queries:
total += (f(l, r) + 1) // 2
return total