🟢 3512. Minimum Operations to Make Array Sum Divisible by K _

tags: 陣列(Array) 數學(Math)

題意

給定一個整數陣列 numsnums 和一個整數 kk 。你可以執行以下操作任意次數:

  • 選擇一個索引 ii 並將 nums[i]nums[i] 替換為 nums[i]1nums[i] - 1

返回使陣列總和可被 kk 整除所需的最少操作次數。

約束條件

  • 1nums.length10001 \leq nums.length \leq 1000
  • 1nums[i]10001 \leq nums[i] \leq 1000
  • 1k1001 \leq k \leq 100

思路:取模運算

注意到我們要求的是使 陣列總和 可被 kk 整除,而每次操作可以使總和減小 11,因此我們只需要計算陣列總和除以 kk 的餘數即可。

複雜度分析

  • 時間複雜度:O(n)O(n)
  • 空間複雜度:O(1)O(1)
1
2
3
class Solution:
def minOperations(self, nums: List[int], k: int) -> int:
return sum(nums) % k
1
2
3
4
5
6
class Solution {
public:
int minOperations(vector<int>& nums, int k) {
return accumulate(nums.begin(), nums.end(), 0) % k;
}
};

🟡 3513. Number of Unique XOR Triplets I _

tags: 陣列(Array) 數學(Math) 位運算(Bit Manipulation) 構造(Construction)

題意

給定一個長度為 nn 的整數陣列 numsnums ,其中 numsnums 是範圍 [1,n][1, n] 中數字的排列。

XOR 三元組定義為三個元素 nums[i]nums[j]nums[k]nums[i] \oplus nums[j] \oplus nums[k] 的 XOR,其中 ijki \leq j \leq k\oplus 表示 XOR 運算。

從所有可能的三元組 (i,j,k)(i, j, k) 中返回唯一 XOR 三元組值的數量。

約束條件

  • 1n1051 \leq n \leq 10^5
  • 1nums[i]n1 \leq nums[i] \leq n
  • numsnums 是從 11nn 的整數排列。

思路:位運算規律歸納與構造

首先,雖然題目給定了一個排列 numsnums,但由於 numsnums[1,n][1, n] 的全排列,實際上我們只需考慮 [1,n][1, n] 這個數集,與排列順序無關。故實際要求的是,從 [1,n][1, n] 中任選三個(可重複)數字 a,b,ca, b, c,計算 abca \oplus b \oplus c 的所有可能結果的種類數。

我們可以嘗試對一個較小的範圍(例如 n[1,102]n \isin [1, 10^2] )寫個暴力程式,觀察 nn 和答案的關係,可以得到以下規律:

  • n<3n < 3 時,答案為 nn
  • n3n \geq 3 時,答案為 2d2^d,其中 ddnn 的二進位長度。

接著我們嘗試證明這個規律。

題目的範例很貼心的展示了 n<3n < 3 時的特殊情況:當 n<3n < 3 時,三元組必定有重複元素。例如 n=1n=1 時,唯一三元組為 (1,1,1)(1,1,1),結果只有 11 種;n=2n=2 時,三元組可能為 (1,1,1)(1,1,1)(1,1,2)(1,1,2)(1,2,2)(1,2,2)(2,2,2)(2,2,2) 等,但由於至少有兩個數相同,實際上只會產生 nn 種不同的 XOR 結果(即 1122)。

接著考慮 n3n \geq 3 的一般情況:

  • nn 的二進位長度為 dd,即 2d1n<2d2^{d-1} \leq n < 2^d。則所有 a,b,ca, b, c 的 XOR 結果最大是 2d12^d-1,因為 [1,n][1, n] 中每個數的最高位都不超過 dd 位。
  • 進一步分析,對於 n3n \geq 3,我們可以構造出 [0,2d1][0, 2^d-1] 內的所有整數。原因如下:
    • 若要得到 00,只需選 (1,2,3)(1, 2, 3) 即可。
    • 若要得到 [1,n][1, n] 內的任意數 xx,只需選 (x,1,1)(x, 1, 1) 即可。
    • 對於 [n+1,2d1][n+1, 2^d-1] 內的任意數 xx,由於 xx 的二進位長度同樣為 dd,因此可以先選 a=na = n,如此便不用再修改最高位,也就是取 b,cb, c 的最高位為 00,只需考慮剩下的 d1d-1 位即可。
      • xx 的第 ii 位和 nn 的第 ii 位相同,可以選擇 b,cb, c 的第 ii 位為 (1,1)(1, 1)
      • xx 的第 ii 位和 nn 的第 ii 位不同,可以選擇 b,cb, c 的第 ii 位為 (0,1)(0, 1)
      • 由於 b,cb, c 的第 dd 位為 00,因此 b,c<nb, c < n,且由於 n>3n > 3d12d - 1 \geq 2,也就是至少有 22 位需要考慮,因此我們也可以總是令 b,c>0b, c > 0;而 n=3n = 3 的結果也已經在範例中展示,因此我們可以總是得到 [n+1,2d1][n+1, 2^d-1] 內的所有整數。
  • 因此,abca \oplus b \oplus c 的所有可能值,正好覆蓋 [0,2d1][0, 2^d-1],共 2d2^d 種。

時間與空間複雜度

  • 時間複雜度:O(1)O(1)
  • 空間複雜度:O(1)O(1)
1
2
3
4
class Solution:
def uniqueXorTriplets(self, nums: List[int]) -> int:
n = len(nums)
return n if n < 3 else 1 << n.bit_length()
1
2
3
4
5
6
7
class Solution {
public:
int uniqueXorTriplets(vector<int>& nums) {
int n = nums.size();
return n < 3 ? n : 1 << (32 - __builtin_clz(n));
}
};

🟡 3514. Number of Unique XOR Triplets II _

tags: 陣列(Array) 數學(Math) 位運算(Bit Manipulation) 枚舉(Enumeration)

題意

給定一個整數陣列 numsnums

XOR 三元組定義為三個元素 nums[i]nums[j]nums[k]nums[i] \oplus nums[j] \oplus nums[k] 的 XOR,其中 ijki \leq j \leq k\oplus 表示 XOR 運算。

從所有可能的三元組 (i,j,k)(i, j, k) 中返回唯一 XOR 三元組值的數量。

約束條件

  • 1n15001 \leq n \leq 1500
  • 1nums[i]15001 \leq nums[i] \leq 1500

思路:枚舉右維護左

首先注意到值域只有 15001500,因此可以不難可以想到 nums[i]nums[j]nums[i] \oplus nums[j] 的值是有限的,由於 XOR 運算並不會增加二進位下的最高位,因此 nums[i]nums[j]nums[i] \oplus nums[j] 最大為 21212^{12}-1,值域為 [0,2121][0, 2^{12}-1],也就是最多只有 20482048 種可能。

因此,我們就可以利用這個特性,以較小的「狀態空間」來記錄所有中間結果,再推導出所有可能的三元組結果。這裡的做法是枚舉 kk,維護 nums[i]nums[j]nums[i] \oplus nums[j] 的結果。

由於三個數可以相等(i=j=ki = j = k),因此在枚舉 kk 時,需要先維護 nums[i]nums[j]nums[i] \oplus nums[j] 的所有可能,再計算所有 (nums[i]nums[j])nums[k](nums[i] \oplus nums[j]) \oplus nums[k] 的結果。

實作時可以先對 numsnums 去重,減少枚舉時 nn 的數量。此外,由於 nums[i]nums[j]nums[i] \oplus nums[j]nums[i]nums[j]nums[k]nums[i] \oplus nums[j] \oplus nums[k] 的值域都只有 [0,2d1][0, 2^d-1],其中 ddnumsnums 中最大值的二進位長度,因此可以用一個 U=2dU = 2^d 的陣列來維護所有可能的中間結果以及最終結果。

複雜度分析

  • 時間複雜度:O(n(n+2d))O(n \cdot (n + 2^{d})),其中 ddnumsnums 中最大值的二進位長度。
  • 空間複雜度:O(2d)O(2^d)
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def uniqueXorTriplets(self, nums: List[int]) -> int:
nums = list(set(nums)) # 去重
n = len(nums)

pre = set([0]) # nums[i] ^ nums[j]
ans = set([nums[0]])
for k in range(1, n):
for i in range(k + 1):
pre.add(nums[i] ^ nums[k]) # nums[i] ^ nums[j]
for p in pre:
ans.add(p ^ nums[k]) # (nums[i] ^ nums[j]) ^ nums[k]
return len(ans)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int uniqueXorTriplets(vector<int>& nums) {
nums.erase(unique(nums.begin(), nums.end()), nums.end()); // 去重
int n = nums.size();
int mx = *max_element(nums.begin(), nums.end());
int U = 1 << (32 - __builtin_clz(mx));
vector<bool> pre(U), ans(U); // 用 vector 代替 set
pre[0] = ans[nums[0]] = true;
for (int k = 1; k < n; k++) {
for (int i = 0; i <= k; i++) // nums[i] ^ nums[j]
pre[nums[i] ^ nums[k]] = true;
for (int i = 0; i < U; i++) // (nums[i] ^ nums[j]) ^ nums[k]
if (pre[i]) ans[i ^ nums[k]] = true;
}
return count(ans.begin(), ans.end(), true);
}
};

🔴 3515. Shortest Path in a Weighted Tree _

tags: 樹(Tree) DFS 線段樹(Segment Tree) 樹狀陣列(Binary Indexed Tree)

題意

給定一個整數 nn 和一個無向加權樹,以節點 11 為根,共有 nn 個節點,編號從 1 到 nn 。這棵樹由一個長度為 n1n - 1 的二維陣列 edgesedges 表示,其中 edges[i]=[ui,vi,wi]edges[i] = [u_i, v_i, w_i] 表示節點 uiu_i 到節點 viv_i 之間的一條無向邊,權重為 wiw_i

另外,給定一個長度為 qq 的二維整數陣列 queriesqueries ,其中每個 queries[i]queries[i] 都是以下兩種情況之一:

  • [1, u, v, w'] – 將節點 uuvv 之間邊的權重更新為 ww' ,其中 (u,v)(u, v) 保證是 edgesedges 中存在的一條邊。
  • [2, x] – 計算從根節點 11 到節點 xx 的最短路徑距離。

返回一個整數陣列 answeranswer ,其中 answer[i]answer[i] 是從節點 11 到節點 xx 的最短路徑距離,對應於第 ii[2,x][2, x] 查詢。

約束條件

  • 1n1051 \leq n \leq 10^5
  • 1wi1041 \leq w_i \leq 10^4
  • 1q1051 \leq q \leq 10^5

思路:維護 DFS 序上的變化量

這道題目要求處理一棵有根樹(根為節點 1)上的兩種操作:更新某條邊的權重,以及查詢從根節點到某個節點的最短路徑距離。由於樹的特性,任意兩點之間的路徑是唯一的,因此從根到某節點的距離可以通過累加路徑上的邊權得到。

直接計算每次查詢的路徑距離效率較低,會導致時間複雜度為 O(qn)O(q \cdot n)。因此,我們需要一種方法來高效處理更新與查詢操作。觀察到 更新某條邊的權重只會影響該邊下方子樹內的所有節點到根的距離 ,這啟發我們可以使用支持 區間更新與查詢(Range Update Range Query) 的資料結構來解決問題。

如果需要維護的是陣列中的某一個區間性質的話,這種 Range Update Range Query 的問題可以用 懶標記線段樹(Lazy Segment Tree)樹狀陣列(Binary Indexed Tree) 維護變化量。

接著思考如何將樹上的某一個子樹視為一個連續區間,注意到當對樹上節點進行 DFS 時,同一個子樹的節點會形成一個連續的區間,因此我們可以透過維護 DFS 序連續區間的變化量來達到目的。

可以搭配以下圖示理解:

graph TD
    %% 原始樹範例
    subgraph "Tree"
        A((1)) --> B((2))
        A --> C((3))
        B --> D((4))
        B --> E((5))
        C --> F((6))
    end
    
    %% 設定節點樣式
    classDef root fill:#f9d5e5,stroke:#333,stroke-width:2px
    classDef node fill:#eeeeee,stroke:#333,stroke-width:1px
    
    %% 套用樣式
    class A root
    class B,C,D,E,F node
graph TD
    %% DFS 序部分
    subgraph "DFS 序與對應區間"
        direction LR
        %% DFS 序節點
        subgraph "DFS 序 (dfns, dfne)"
            N1["節點 1: (1, 6)"]
            N2["節點 2: (2, 4)"]
            N4["節點 4: (3, 3)"]
            N5["節點 5: (4, 4)"]
            N3["節點 3: (5, 6)"]
            N6["節點 6: (6, 6)"]
        end
        
        %% 子樹區間
        subgraph "子樹對應區間 [dfns[u]..dfne[u]]"
            R1["子樹 1: [1..6]"]
            R2["子樹 2: [2..4]"]
            R3["子樹 3: [5..6]"]
            R4["子樹 4: [3..3]"]
            R5["子樹 5: [4..4]"]
            R6["子樹 6: [6..6]"]
        end
    end
    
    %% 設定節點樣式
    classDef dfnsNode fill:#d4f1f9,stroke:#333,stroke-width:1px
    classDef arrayNode fill:#e3f2fd,stroke:#333,stroke-width:1px
    classDef rangeNode fill:#e8f5e9,stroke:#333,stroke-width:1px
    
    %% 套用樣式
    class N1,N2,N3,N4,N5,N6 dfnsNode
    class Arr arrayNode
    class R1,R2,R3,R4,R5,R6 rangeNode

但需要注意,由於修改 u,vu, v 的邊權,只會影響 u,vu, v 的子樹,因此需要先判斷 u,vu, v 中哪個是子節點,再進行區間更新。由於題目保證 ww 是正數,因此這裡可以直接用 distdist 來判斷哪個是子節點,否則需要維護 parentparent 陣列。

此外,保存邊權的時候,可以直接保存 (u,v)(u, v) 這個 tuple,這樣在更新邊權的時候,可以直接透過 mp[(u, v)] 來取得邊權,而不需要再花時間找邊權。也可以利用樹中每個節點都只有一個父節點的特性,在 DFS 的時候,直接 把邊權保存到子節點 上,這樣在更新邊權的時候,可以直接透過 weight[v] 來取得邊權,不用使用雜湊表。

複雜度分析

  • 時間複雜度:O((n+q)logn)O((n + q) \log n)
  • 空間複雜度:O(n)O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
class SegNode:
def __init__(self) -> None:
self.ls = self.rs = None # left and right child
self.val = self.lazy = 0 # value, lazy tag

class SegmentTree:
def __init__(self, nums: List[int] = None):
self.nums = nums
self.root = SegNode()
if nums is not None and len(nums) > 0:
self.build(self.root, 1, len(nums))

def build(self, node: SegNode, left: int, right: int) -> None:
if left == right:
node.val = self.nums[left - 1]
return
mid = (left + right) // 2
node.ls = SegNode()
node.rs = SegNode()
self.build(node.ls, left, mid)
self.build(node.rs, mid + 1, right)
self.pushup(node) # push up node value

# update the range [l, r] with value v
@staticmethod
def update(node: SegNode, left: int, right: int, l: int, r: int, v: int) -> None:
if l <= left and right <= r:
# update node value (Customized)
SegmentTree._update(node, left, right, v)
return
SegmentTree.pushdown(node, left, right) # push down lazy tags
mid = (left + right) // 2
if l <= mid:
SegmentTree.update(node.ls, left, mid, l, r, v)
if r > mid:
SegmentTree.update(node.rs, mid + 1, right, l, r, v)
SegmentTree.pushup(node) # push up node value

# update node value (Customized)
@staticmethod
def _update(node: SegNode, left: int, right: int, v: int) -> None:
node.val += v * (right - left + 1)
node.lazy += v

# query the range [l, r]
@staticmethod
def query(node: SegNode, left: int, right: int, l: int, r: int) -> int:
if l <= left and right <= r:
return node.val
# Ensure all lazy tags have been pushed down
SegmentTree.pushdown(node, left, right)
mid = (left + right) // 2
# Calculate answer (Customized)
ans = 0
if l <= mid:
ans += SegmentTree.query(node.ls, left, mid, l, r)
if r > mid:
ans += SegmentTree.query(node.rs, mid + 1, right, l, r)
return ans

# push down lazy tags
@staticmethod
def pushdown(node: SegNode, left: int, right: int) -> None:
if node.ls is None:
node.ls = SegNode()
if node.rs is None:
node.rs = SegNode()
if node.lazy != 0:
# Update node value (Customized)
mid = (left + right) // 2
SegmentTree._update(node.ls, left, mid, node.lazy)
SegmentTree._update(node.rs, mid + 1, right, node.lazy)
node.lazy = 0

# push up node value
@staticmethod
def pushup(node: SegNode) -> None:
# Update method (Customized)
node.val = node.ls.val + node.rs.val

class Solution:
def treeQueries(self, n: int, edges: List[List[int]], queries: List[List[int]]) -> List[int]:
g = defaultdict(list)
mp = {}
for u, v, w in edges:
if u > v:
u, v = v, u
g[u].append((v, w))
g[v].append((u, w))
mp[(u, v)] = w

dist = [0] * (n + 1)
dfns = [0] * (n + 1)
dfne = [0] * (n + 1)
time = 0 # timestamp, 1-indexed

def dfs(u, fa, d):
nonlocal time
time += 1
dfns[u] = time
dist[u] = d
for v, w in g[u]:
if v == fa:
continue
dfs(v, u, d + w)
dfne[u] = time

dfs(1, 0, 0)
st = SegmentTree([0] * n) # 維護變化量

ans = []
for query in queries:
op, *args = query
if op == 1:
u, v, w = args
if u > v:
u, v = v, u
c = u if dist[u] > dist[v] else v # 判斷哪個是子節點
st.update(st.root, 1, n, dfns[c], dfne[c], w - mp[(u, v)])
mp[(u, v)] = w
elif op == 2:
x = args[0]
ans.append(dist[x] + st.query(st.root, 1, n, dfns[x] , dfns[x]))
return ans
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
template<typename T>
struct SegNode {
SegNode *ls, *rs; // left child, right child
T val, lazy; // value, lazy tag
SegNode() : ls(nullptr), rs(nullptr), val(0), lazy(0) {}
};

template<typename T>
class SegmentTree {
public:
SegNode<T> *root;

SegmentTree() {
root = new SegNode<T>();
}

SegmentTree(vector<T> &nums) {
root = new SegNode<T>();
build(root, 1, nums.size(), nums);
}

void build(SegNode<T> *node, int left, int right, vector<T> &nums) {
if (left == right) {
node->val = nums[left - 1]; // assert nums is 0-indexed
return;
}
int mid = left + ((right - left) >> 1);
node->ls = new SegNode<T>();
node->rs = new SegNode<T>();
build(node->ls, left, mid, nums);
build(node->rs, mid + 1, right, nums);
pushup(node); // Push up node value
}

// Update the range [l, r] with value v
void update(SegNode<T> *node, int left, int right, int l, int r, T v) {
if (l <= left && right <= r) {
// Update node value (Customized)
_update(node, left, right, v);
return;
}
pushdown(node, left, right);
int mid = left + ((right - left) >> 1);
if (l <= mid) update(node->ls, left, mid, l, r, v);
if (r > mid) update(node->rs, mid + 1, right, l, r, v);
pushup(node); // Push up node value
}

// Update node value (Customized)
void _update(SegNode<T> *node, int left, int right, T v) {
node->val += v * (right - left + 1);
node->lazy += v;
}

// Query the range [l, r]
T query(SegNode<T> *node, int left, int right, int l, int r) {
if (l <= left && right <= r) {
return node->val;
}
pushdown(node, left, right);
int mid = left + ((right - left) >> 1);
// Calculate answer (Customized)
T ans = 0;
if (l <= mid) ans += query(node->ls, left, mid, l, r);
if (r > mid) ans += query(node->rs, mid + 1, right, l, r);
return ans;
}

// Push down lazy tags
void pushdown(SegNode<T> *node, int left, int right) {
if (node->ls == nullptr) node->ls = new SegNode<T>();
if (node->rs == nullptr) node->rs = new SegNode<T>();
if (node->lazy != 0) {
int mid = left + ((right - left) >> 1);
// Update node value (Customized)
_update(node->ls, left, mid, node->lazy);
_update(node->rs, mid + 1, right, node->lazy);
node->lazy = 0;
}
}

// Push up node value
void pushup(SegNode<T> *node) {
// Update method (Customized)
node->val = node->ls->val + node->rs->val;
}
};

class Solution {
public:
vector<int> treeQueries(int n, vector<vector<int>>& edges, vector<vector<int>>& queries) {
vector<vector<pair<int, int>>> g(n + 1);
for (auto &edge : edges) {
int u = edge[0], v = edge[1], w = edge[2];
g[u].emplace_back(v, w);
g[v].emplace_back(u, w);
}

vector<int> dist(n + 1), dfns(n + 1), dfne(n + 1), weight(n + 1);
int time = 0; // timestamp, 1-indexed
auto dfs = [&](this auto &&dfs, int u, int fa, int d) -> void {
dfns[u] = ++time;
dist[u] = d;
for (auto [v, w] : g[u]) {
if (v == fa) continue;
weight[v] = w; // store weight at child node
dfs(v, u, d + w);
}
dfne[u] = time;
};
dfs(1, 0, 0);

SegmentTree<long long> st;
vector<int> ans;
for (auto &query : queries) {
int op = query[0];
if (op == 1) {
int u = query[1], v = query[2], w = query[3];
if (dist[u] > dist[v]) swap(u, v); // let v be the child node
st.update(st.root, 1, n, dfns[v], dfne[v], w - weight[v]);
weight[v] = w; // update weight
} else {
int x = query[1];
ans.push_back(dist[x] + st.query(st.root, 1, n, dfns[x], dfns[x]));
}
}
return ans;
}
};

寫在最後

PROMPT

masterpiece, best quality, high quality, extremely detailed CG unity 8k wallpaper, extremely detailed, High Detail, vibrant colors, anime style, ((artist:reoen), (artist:fuzichoco), (artist:atdan), (artist:torino_aqua), year 2024:0.8), [artist:wlop], [artist:ningen_mame], artist:ciloranko, [[artist:rhasta]], artist:tidsean,

1girl, solo, Mumei, Nanashi Mumei, virtual youtuber, vtuber, brown hair, long hair, very long hair, brown eyes, hair ornament, feather, sitting pose, elegant white dress with floral embellishments, sheer sleeves with pearl details, dimly lit background with soft lighting,

The image features a character with long brown hair and brown eyes, dressed in an elegant white dress adorned with floral patterns and sheer sleeves decorated with pearls. She is seated in a dimly lit setting with a soft, warm glow illuminating her figure.

這場打得有點隨興,Q2 直接暴力打表猜了結論,Q3 也是看到值域後直接寫了個很接近很暴力的東西,幸好沒有在這種手速場吃到 WA XD