---
# System prepended metadata

title: 10/3 資結小考 參考題解

---

# 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", &current);
        
        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;
}
```