# 10/3 資結小考 參考題解
## Problem 810
**題目:**
- N個便當疊成一堆,有K種口味(1到K)
- 只能賣最上面的便當
- N個客人排隊,每人想要特定口味
- 購買過程:
- 如果最上面便當是想要的口味→買走離開
- 如果不是→回到隊伍最後重新排隊
- 直到最上面便當沒有任何排隊的人想要時停止
- 輸出:最終買不到便當的人數
**子題限制:**
- 子題1 (20%): n ≤ 10, k ≤ 10
- 子題2 (30%): n ≤ 100
- 子題3 (50%): 1 ≤ n ≤ 10^7, 1 ≤ k ≤ 1000
### 暴力解(50%)
以最簡單直覺的想法: 做兩個deque來直接模擬排隊和便當的狀況。
每次檢查最上面便當時,就遍歷整個隊伍找想要的客人。
時間複雜度O(N²),子題1,2可過,但子題3會TLE。
```c
#include <stdio.h>
#include <stdlib.h>
#define maxN 10000005
#define maxK 1005
typedef struct {
int data[maxN];
int front, rear, size;
} Deque;
Deque list;
Deque queue;
int people[maxK];
void init(Deque* dq) {
dq->front = 0;
dq->rear = 0;
dq->size = 0;
}
int empty(Deque* dq) {
return dq->size == 0;
}
void push_back(Deque* dq, int value) {
dq->data[dq->rear] = value;
dq->rear = (dq->rear + 1) % maxN;
dq->size++;
}
void push_front(Deque* dq, int value) {
dq->front = (dq->front - 1 + maxN) % maxN;
dq->data[dq->front] = value;
dq->size++;
}
int pop_front(Deque* dq) {
if (empty(dq)) return -1;
int value = dq->data[dq->front];
dq->front = (dq->front + 1) % maxN;
dq->size--;
return value;
}
int pop_back(Deque* dq) {
if (empty(dq)) return -1;
dq->rear = (dq->rear - 1 + maxN) % maxN;
int value = dq->data[dq->rear];
dq->size--;
return value;
}
int peek_back(Deque* dq) {
if (empty(dq)) return -1;
return dq->data[(dq->rear - 1 + maxN) % maxN];
}
int main() {
int N, K;
scanf("%d %d", &N, &K);
init(&list);
init(&queue);
for (int i = 0; i < N; i++) {
int tmp;
scanf("%d", &tmp);
push_back(&list, tmp);
}
for (int i = 0; i < N; i++) {
int tmp;
scanf("%d", &tmp);
push_back(&queue, tmp);
people[tmp]++;
}
while (!empty(&queue)) {
int now = peek_back(&list);
if (people[now] == 0) {
break;
}
int found = 0;
int queueSize = queue.size;
for (int i = 0; i < queueSize; i++) {
int customer = pop_front(&queue);
if (customer == now && !found) {
pop_back(&list);
people[now]--;
found = 1;
} else {
push_back(&queue, customer);
}
}
if (!found) break;
}
printf("%d\n", queue.size);
return 0;
}
```
### 解法(100%AC)
觀察第三個子題,N最大是1e7,原先作法肯定會TLE。從最壞情況觀察,每個便當都可能需要排隊輪過一輪才知道是否結束,意味著隊伍裡已經沒有想要買該口味便當的顧客。可以建立K大小的查表,記錄每種口味需求人數,不再需要花費O(N)時間維護排隊。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define maxN 10000000
#define maxK 1000
int people[maxK+5] = {0};
int list[maxN+5];
int pt = 0;
int main(){
int N, K;
scanf("%d %d", &N, &K);
int tmp;
for(int i=0; i<N; i++){
scanf("%d", &tmp);
list[pt++] = tmp;
}
for(int i=0; i<N; i++){
scanf("%d", &tmp);
people[tmp]++;
}
for(int i=0; i<N; i++){
int now = list[pt-1];
if(!people[now]) break;
else people[now]--;
pt--;
}
int count = pt;
printf("%d", count);
}
```
---
## Problem 811
**題目:**
- N個觀眾排隊,編號1到N (1 ≤ N ≤ 10^5)
- Q個指令 (1 ≤ Q ≤ 10^5)
- 每個指令包含兩個數字A和B
- 將編號A到編號B的所有觀眾移動到隊伍最後面
- 保證A和B都在隊伍中,且執行時A的位置 ≤ B的位置
- 移動的觀眾之間相對位置保持不變
- 輸出:執行完所有指令後的最終排列順序
**子題限制:**
- 子題1 (20分): n ≤ 10, q ≤ 10
- 子題2 (30分): n ≤ 1000, q ≤ 1000
- 子題3 (50分): 1 ≤ n ≤ 10^5, 1 ≤ q ≤ 10^5
### 暴力解(50%)
以最直覺的想法: 建立linkedlist,手動將目標數值位置找出,並一個一個丟到最後面。時間複雜度較高,測資除了最後一筆外都會過。
```c
//測資除了最後一筆外都會過,可見助教有多仁慈
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int value;
struct Node* prev;
struct Node* next;
} Node;
Node* head = NULL;
Node* tail = NULL;
int N, Q;
// 創建新節點
Node* createNode(int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->value = value;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
// 找到值為value的節點
Node* findNode(int value) {
Node* current = head;
while (current != NULL) {
if (current->value == value) return current;
current = current->next;
}
return NULL;
}
// 移除一個節點
void removeNode(Node* node) {
if (node->prev != NULL) {
node->prev->next = node->next;
} else {
head = node->next;
}
if (node->next != NULL) {
node->next->prev = node->prev;
} else {
tail = node->prev;
}
node->prev = node->next = NULL;
}
// 將節點加到最後面
void appendNode(Node* node) {
if (tail != NULL) {
tail->next = node;
node->prev = tail;
} else {
head = node;
node->prev = NULL;
}
tail = node;
node->next = NULL;
}
int main() {
scanf("%d %d", &N, &Q);
// 建立初始鏈結串列
for (int i = 0; i < N; i++) {
int value;
scanf("%d", &value);
Node* newNode = createNode(value);
if (head == NULL) {
head = tail = newNode;
} else {
tail->next = newNode;
newNode->prev = tail;
tail = newNode;
}
}
// 處理Q個操作
for (int i = 0; i < Q; i++) {
int A, B;
scanf("%d %d", &A, &B);
// 從A開始,一個一個拿到後面
Node* current = findNode(A);
Node* endNode = findNode(B);
while (current != NULL) {
Node* next = current->next;
// 移除當前節點
removeNode(current);
// 加到最後面
appendNode(current);
// 如果到達B就停止
if (current == endNode) break;
current = next;
}
}
// 輸出結果
Node* current = head;
while (current != NULL) {
printf("%d ", current->value);
current = current->next;
}
printf("\n");
// 釋放記憶體
current = head;
while (current != NULL) {
Node* temp = current;
current = current->next;
free(temp);
}
return 0;
}
```
### Array_Linkedlist解(100%AC)
從第三個子題來看,N和Q上限是1e5,O(NQ)肯定會超時,因此需要優化。題目條件「相對位置不變」很重要,以linkedlist特性來看,只需對切割區間的頭尾做操作,複雜度O(1),問題在於如何找到數字。
依題目敘述,數字是1~N,可以利用array連續記憶體特性,在array上模擬linkedlist,每個index代表每個數字,不再需要花線性時間尋找數字,使得對數字直接操作成為可能。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define maxN 100000
#define PREV 0
#define NEXT 1
int N, Q;
int nodes[maxN + 10][2]; // [i][PREV] = 前一個節點, [i][NEXT] = 後一個節點
int head = -1;
int tail = -1;
int main() {
scanf("%d %d", &N, &Q);
memset(nodes, -1, sizeof(nodes));
// 建立初始鏈結串列
int prev = -1;
int current;
for (int i = 0; i < N; i++) {
scanf("%d", ¤t);
if (head == -1) {
head = current;
} else {
nodes[current][PREV] = prev;
nodes[prev][NEXT] = current;
}
prev = tail = current;
}
// 處理 Q 個查詢
int rangeStart, rangeEnd;
for (int i = 0; i < Q; i++) {
scanf("%d %d", &rangeStart, &rangeEnd);
int prevOfStart = nodes[rangeStart][PREV];
int nextOfEnd = nodes[rangeEnd][NEXT];
// 特殊情況:如果要移動的已經在最後面,跳過
if (head == rangeStart && nextOfEnd == -1) {
continue;
}
// 切斷區間:修復切斷後的連結
if (prevOfStart != -1) {
nodes[prevOfStart][NEXT] = nextOfEnd;
} else {
head = nextOfEnd; // rangeStart 是第一個節點
}
if (nextOfEnd != -1) {
nodes[nextOfEnd][PREV] = prevOfStart;
} else {
tail = prevOfStart; // rangeEnd 是最後一個節點
}
// 將切斷的區間接到尾巴
nodes[tail][NEXT] = rangeStart;
nodes[rangeStart][PREV] = tail;
nodes[rangeEnd][NEXT] = -1;
tail = rangeEnd;
}
// 輸出最終結果
current = head;
while (current != -1) {
printf("%d ", current);
current = nodes[current][NEXT];
}
printf("\n");
return 0;
}
```