Skip to content

Commit 8020393

Browse files
committed
Add: Add 2025/11/6
1 parent 381cc53 commit 8020393

File tree

3 files changed

+527
-0
lines changed

3 files changed

+527
-0
lines changed
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# 3607. Power Grid Maintenance
2+
3+
You are given an integer `c` representing `c` power stations, each with a unique identifier `id` from 1 to `c` (1‑based indexing).
4+
5+
These stations are interconnected via n bidirectional cables, represented by a 2D array connections,
6+
where each element `connections[i] = [u_i, v_i]` indicates a connection between station `u_i` and station `v_i`.
7+
Stations that are directly or indirectly connected form a power grid.
8+
9+
Initially, all stations are online (operational).
10+
11+
You are also given a 2D array `queries`, where each query is one of the following two types:
12+
13+
- `[1, x]`: A maintenance check is requested for station `x`.
14+
If station `x` is online, it resolves the check by itself.
15+
If station `x` is offline, the check is resolved by the operational station with the smallest `id` in the same power grid as `x`.
16+
If no operational station exists in that grid, return -1.
17+
18+
- `[2, x]`: Station `x` goes offline (i.e., it becomes non-operational).
19+
20+
Return an array of integers representing the results of each query of type `[1, x]` in the order they appear.
21+
22+
Note: The power grid preserves its structure; an offline (non‑operational) node remains part of its grid and taking it offline does not alter connectivity.
23+
24+
**Constraints:**
25+
26+
- `1 <= c <= 10^5`
27+
- `0 <= n == connections.length <= min(10^5, c * (c - 1) / 2)`
28+
- `connections[i].length == 2`
29+
- `1 <= u_i, v_i <= c`
30+
- `u_i != v_i`
31+
- `1 <= queries.length <= 2 * 10^5`
32+
- `queries[i].length == 2`
33+
- `queries[i][0]` is either `1` or `2`.
34+
- `1 <= queries[i][1] <= c`
35+
36+
## 基礎思路
37+
38+
本題要我們模擬一個電網系統,支援「查詢維修」與「停機」操作,並且在大量節點與查詢下維持高效執行。
39+
40+
題意可歸納為:
41+
42+
1. 每個電站具有唯一 ID(1-based)。
43+
2. 電站之間以雙向電纜連線,形成若干「電網(連通分量)」。
44+
3. 每次查詢可能是:
45+
- **`[1, x]` 維修請求**:若 `x` 在線上,輸出 `x`;若 `x` 離線,輸出同一電網中最小編號的線上電站;若整網皆離線,輸出 `-1`
46+
- **`[2, x]` 停機請求**:將電站 `x` 標記為離線。
47+
4. 電網結構不會因停機而改變(停機不會斷線)。
48+
49+
在思考解法時,我們需關注幾個重點:
50+
51+
- **電網識別**:要能快速判斷兩電站是否屬於同一電網。
52+
- **最小線上節點查找**:需能在某個電網內找到最小線上電站,且當節點停機後仍能有效更新。
53+
- **高效查詢**:題目上限達 $2 \times 10^5$ 次查詢,必須避免逐次遍歷整個電網。
54+
55+
為達成以上要求,我們採取以下策略:
56+
57+
- **使用並查集(Union-Find)**:先將所有電站依照電纜連線合併成連通分量,確定每個電站所屬電網。
58+
- **分量壓縮與排序**:將每個電網的電站以 ID 升序排列,並記錄每個電網的起訖區間位置。
59+
- **指標維護**:對每個電網維護一個指標,指向目前最小的線上節點;若該節點停機,指標向後推進。
60+
- **懶惰推進策略(Lazy advancement)**:只有當某電網被查詢或停機時才更新指標,避免重複掃描。
61+
62+
透過此設計,查詢與停機皆可在近似常數時間內完成,滿足題目的效能需求。
63+
64+
## 解題步驟
65+
66+
### Step 1:處理無電纜情況
67+
68+
若沒有任何電纜,所有電站皆獨立成網,查詢僅需判斷該站是否離線。
69+
70+
```typescript
71+
// 若無任何連線,每個電站皆獨立,直接以單節點方式處理
72+
if (edgeCount === 0) {
73+
const isOffline = new Uint8Array(stationCount + 1); // 標記每個電站是否離線
74+
const results: number[] = [];
75+
76+
for (let i = 0; i < queries.length; i += 1) {
77+
const queryType = queries[i][0] | 0;
78+
const stationId = queries[i][1] | 0;
79+
80+
if (queryType === 2) {
81+
// 將指定電站標記為離線
82+
isOffline[stationId] = 1;
83+
continue;
84+
}
85+
86+
// 維修查詢:若在線上輸出自身,否則輸出 -1
87+
if (isOffline[stationId] === 0) {
88+
results.push(stationId);
89+
} else {
90+
results.push(-1);
91+
}
92+
}
93+
return results;
94+
}
95+
```
96+
97+
### Step 2:建立並查集
98+
99+
並查集(Union-Find)能快速判定兩節點是否屬於同一電網。
100+
101+
```typescript
102+
// 建立並查集結構
103+
const parent = new Int32Array(stationCount + 1);
104+
const setSize = new Int32Array(stationCount + 1);
105+
106+
// 初始化:每個節點自成一組
107+
for (let stationId = 1; stationId <= stationCount; stationId += 1) {
108+
parent[stationId] = stationId;
109+
setSize[stationId] = 1;
110+
}
111+
112+
/**
113+
* 尋找節點的根節點(帶路徑壓縮)
114+
*/
115+
function findRoot(stationId: number): number {
116+
let current = stationId | 0;
117+
while (parent[current] !== current) {
118+
parent[current] = parent[parent[current]]; // 路徑壓縮
119+
current = parent[current];
120+
}
121+
return current;
122+
}
123+
124+
/**
125+
* 合併兩個集合(按大小合併以保持樹淺)
126+
*/
127+
function mergeSets(firstStation: number, secondStation: number): void {
128+
let rootA = findRoot(firstStation);
129+
let rootB = findRoot(secondStation);
130+
131+
if (rootA === rootB) return; // 已屬同一電網
132+
133+
if (setSize[rootA] < setSize[rootB]) {
134+
const temp = rootA;
135+
rootA = rootB;
136+
rootB = temp;
137+
}
138+
139+
parent[rootB] = rootA;
140+
setSize[rootA] += setSize[rootB];
141+
}
142+
```
143+
144+
### Step 3:合併所有連線
145+
146+
依序合併每條連線,將相連的電站合併為同一電網。
147+
148+
```typescript
149+
// 根據連線建立電網結構
150+
for (let i = 0; i < edgeCount; i += 1) {
151+
const u = connections[i][0] | 0;
152+
const v = connections[i][1] | 0;
153+
mergeSets(u, v);
154+
}
155+
```
156+
157+
### Step 4:壓縮電網索引
158+
159+
將每個根節點分配獨立的電網編號,建立「節點 → 電網」的對應關係。
160+
161+
```typescript
162+
// 將每個電網根節點映射成獨立的電網編號
163+
const rootToComponent = new Int32Array(stationCount + 1);
164+
let componentCount = 0;
165+
166+
for (let stationId = 1; stationId <= stationCount; stationId += 1) {
167+
const root = findRoot(stationId);
168+
if (rootToComponent[root] === 0) {
169+
componentCount += 1;
170+
rootToComponent[root] = componentCount;
171+
}
172+
}
173+
```
174+
175+
### Step 5:建立每個電網的節點清單
176+
177+
依電網索引將電站排序並集中儲存,方便後續快速查詢最小線上節點。
178+
179+
```typescript
180+
// 建立電網中電站的排序資訊
181+
const stationToComponent = new Int32Array(stationCount + 1);
182+
const orderedStations = new Int32Array(stationCount);
183+
const componentSize = new Int32Array(componentCount + 1);
184+
185+
// 統計每個電網的節點數
186+
for (let id = 1; id <= stationCount; id += 1) {
187+
const root = findRoot(id);
188+
const comp = rootToComponent[root];
189+
stationToComponent[id] = comp;
190+
componentSize[comp] += 1;
191+
}
192+
193+
// 計算每個電網在 orderedStations 中的起訖位置(prefix sum)
194+
const start = new Int32Array(componentCount + 1);
195+
const end = new Int32Array(componentCount + 1);
196+
let offset = 0;
197+
for (let comp = 1; comp <= componentCount; comp += 1) {
198+
start[comp] = offset;
199+
offset += componentSize[comp];
200+
end[comp] = offset;
201+
}
202+
```
203+
204+
### Step 6:填入每個電網的節點並初始化指標
205+
206+
每個電網的節點在 `orderedStations` 內依 ID 升序排列,並為每個電網設置指標指向最小線上節點。
207+
208+
```typescript
209+
// 每個電網的填寫游標
210+
const writeCursor = new Int32Array(componentCount + 1);
211+
for (let comp = 1; comp <= componentCount; comp += 1) {
212+
writeCursor[comp] = start[comp];
213+
}
214+
215+
// 將節點依序填入 orderedStations
216+
for (let id = 1; id <= stationCount; id += 1) {
217+
const comp = stationToComponent[id];
218+
const pos = writeCursor[comp];
219+
orderedStations[pos] = id;
220+
writeCursor[comp] = pos + 1;
221+
}
222+
223+
// 初始化每個電網的指標
224+
const pointer = new Int32Array(componentCount + 1);
225+
for (let comp = 1; comp <= componentCount; comp += 1) {
226+
pointer[comp] = start[comp];
227+
}
228+
```
229+
230+
### Step 7:定義輔助函數
231+
232+
該函數用來在某電網內前進指標,跳過所有已離線節點。
233+
234+
```typescript
235+
/**
236+
* 移動電網指標以跳過離線節點
237+
*/
238+
function movePointerForward(componentIndex: number): void {
239+
let p = pointer[componentIndex];
240+
const endPos = end[componentIndex];
241+
242+
while (p < endPos) {
243+
const id = orderedStations[p];
244+
if (isOffline[id] === 0) break; // 找到線上節點
245+
p += 1;
246+
}
247+
pointer[componentIndex] = p;
248+
}
249+
```
250+
251+
### Step 8:處理所有查詢
252+
253+
根據查詢類型更新離線狀態或回傳維修結果。
254+
255+
```typescript
256+
// 處理查詢
257+
const isOffline = new Uint8Array(stationCount + 1);
258+
const results: number[] = [];
259+
260+
for (let i = 0; i < queries.length; i += 1) {
261+
const type = queries[i][0] | 0;
262+
const id = queries[i][1] | 0;
263+
264+
if (type === 2) {
265+
// 停機事件:標記離線並更新指標
266+
isOffline[id] = 1;
267+
const comp = stationToComponent[id];
268+
const p = pointer[comp];
269+
if (p < end[comp] && orderedStations[p] === id) {
270+
movePointerForward(comp);
271+
}
272+
continue;
273+
}
274+
275+
// 維修查詢
276+
if (isOffline[id] === 0) {
277+
results.push(id); // 在線上,輸出自身
278+
continue;
279+
}
280+
281+
// 離線:尋找同電網中最小的線上電站
282+
const comp = stationToComponent[id];
283+
movePointerForward(comp);
284+
const pNow = pointer[comp];
285+
if (pNow >= end[comp]) {
286+
results.push(-1); // 全電網離線
287+
} else {
288+
results.push(orderedStations[pNow]); // 回傳最小線上節點
289+
}
290+
}
291+
```
292+
293+
## 時間複雜度
294+
295+
- **預處理(並查集 + 佈局)**
296+
建立 DSU 並合併所有連邊為 $O(m,\alpha(n))$,壓縮與分塊佈局為 $O(n)$。
297+
其中 $m$ 為電纜數,$n$ 為電站數,$\alpha$ 為阿克曼反函數。
298+
- **線上查詢**
299+
每個連通塊的游標總前移次數至多等於該塊站數,因此所有 `[1]``[2]` 操作的總攤銷時間為 $O(q + n)$。
300+
其中 $q$ 為查詢數。
301+
- 總時間複雜度為 $O(n + m,\alpha(n) + q)$。
302+
303+
> $O(n + m,\alpha(n) + q)$
304+
305+
## 空間複雜度
306+
307+
- DSU 陣列、電網分塊、排序後站台、游標與離線標記陣列皆為線性規模。
308+
- 需額外 $O(n)$ 儲存索引與輔助結構。
309+
- 總空間複雜度為 $O(n)$。
310+
311+
> $O(n)$

0 commit comments

Comments
 (0)