Test Message

d373: 5. 乳酪的誘惑

內容

這個問題跟傳統的老鼠走迷宮問題不太一樣,給定一個四方形的格點空間
x 座標與 y 座標由左下角原點以 0 開始往右和往上遞增,裡面有一個區域是鼠窩,另外有一個區域是乳酪屋
此外還有很多障礙物是用來阻擋鼠輩的橫行,也就是障礙物是不可穿越的。
在此空間裡可能沒有路可以從鼠窩走到乳酪屋,此種情況你必須回報沒有路徑
如果有路可以連接鼠窩跟乳酪屋的話,你必須算出從鼠窩走到乳酪屋的最短路徑。
老鼠的每一步只能往四個方向走:上、下、左、右,不能走對角方向。出發點與終點可以是鼠窩與乳酪屋的任何一個位置。
以圖一為例,鼠窩到乳酪屋的最短距離為 15,由鼠窩裡圓圈所標示的點(10,2)往左出發走到乳酪屋中圓圈所標示的點(1,8)。
鼠窩與乳酪屋的形狀一定是一條直線,障礙物可以是一點可以是一條直線也可以是一個矩形區域
圖一中的障礙物是由三個點與兩條直線所構成,分別為點(2,6)、(3,5)、與(4,4)和線(4,3)~(7,3)與(7,4)~(7,9)。
在找尋最短路徑時,不要只派遣一隻老鼠出任務,這樣會累死這隻可憐的倒霉鼠的。

img1


輸入

輸入的第一行為兩個整數,描述方形格點空間的大小,第一個表示行的數目,第二個表示列的數目
在此問題中行和列的數目都不會超過 1000 (<=1000)。
接下來“.mb”表示後面用兩點來描述鼠窩的位置,每個點先 x 座標再 y 座標,兩個點沒有固定的先後排序。
接下來“.cb”表示後面用兩點來描述乳酪屋的位置,兩個點也沒有固定的先後排序。
最後“.bb”表示後面開始每一行描述一個障礙物的位置,不管是一點還是一條線都是用兩點來表示
最後以“.be”表示結束。

以圖一為例,其輸入檔案如下:
14 10
.mb 10 4 10 1
.cb 1 8 4 8
.bb
2 6 2 6
3 5 3 5
4 4 4 4
4 3 7 3
7 9 7 4
.be

輸出

沒有路徑的話,輸出“no path”。有路徑的話輸出最短路徑距離。

15


解題思路

強化版的尋路問題,因為可以從鼠窩的任意點出發且抵達乳酪屋的任意點及代表結束,因此將所有鼠窩的點都當作起點,使用 Dijkstra 從所有起點向外擴張,直到擴張至乳酪屋時結束。擴張的總輪數即為鼠窩移動至乳酪屋的最短距離,輸出該值即可。

  • 地圖很大,使用佇列紀錄下次擴張的位置。

  • 每個測資點僅一筆測資且鼠窩和乳酪屋皆只有一筆資料。


完整程式碼

AC (8ms, 4MB)
#include <stdio.h>
#define MAX 1010
#define SWAP(x, y) (x)^=((y)^=((x)^=(y)))

typedef struct
{
int x, y;
}Point;

Point quene1[MAX * 4], quene2[MAX * 4];
int map[MAX][MAX];
char input[100];

inline char* getUInt(register char buffer[], register unsigned int* dst)
{
while (*buffer < '0')
if (!*buffer++) return NULL;
*dst = *buffer ^ '0';
while (*++buffer >= '0')
* dst = (*dst << 3) + (*dst << 1) + (*buffer ^ '0');
return buffer;
}

inline void setMap(int map[][MAX], Point s, Point e, int val)
{
if (e.x < s.x) SWAP(e.x, s.x);
if (e.y < s.y) SWAP(e.y, s.y);
for (int i = s.x; i <= e.x; i++)
{
for (int j = s.y; j <= e.y; j++)
map[i][j] = val;
}
}

int getAns(int row, int col, register Point* nowTop)
{
Point* now = quene1, * next = quene2, * nextTop, * tmp;
for (int step = 1;; step++)
{
if (nowTop == now)
return -1;
nextTop = next;
while (now <= --nowTop)
{
if (map[nowTop->x][nowTop->y]) continue;
map[nowTop->x][nowTop->y] = step;
if (nowTop->x > 0)
{
if (map[nowTop->x - 1][nowTop->y])
{
if (map[nowTop->x - 1][nowTop->y] == -2)
return step;
}
else nextTop->x = nowTop->x - 1, nextTop++->y = nowTop->y;
}
if (nowTop->x < row)
{
if (map[nowTop->x + 1][nowTop->y])
{
if (map[nowTop->x + 1][nowTop->y] == -2)
return step;
}
else nextTop->x = nowTop->x + 1, nextTop++->y = nowTop->y;
}
if (nowTop->y > 0)
{
if (map[nowTop->x][nowTop->y - 1])
{
if (map[nowTop->x][nowTop->y - 1] == -2)
return step;
}
else nextTop->x = nowTop->x, nextTop++->y = nowTop->y - 1;
}
if (nowTop->y < col)
{
if (map[nowTop->x][nowTop->y + 1])
{
if (map[nowTop->x][nowTop->y + 1] == -2)
return step;
}
else nextTop->x = nowTop->x, nextTop++->y = nowTop->y + 1;
}
}
tmp = now, now = next, next = tmp;
nowTop = nextTop;
}
}

int main()
{
char* st;
int n, m, ans;
Point s, e, * nowTop = quene1;
scanf("%d %d", &n, &m);
scanf("%*s %d %d %d %d", &s.x, &s.y, &e.x, &e.y);
if (e.x < s.x) SWAP(e.x, s.x);
if (e.y < s.y) SWAP(e.y, s.y);
int idx = 0;
for (int i = s.x; i <= e.x; i++)
{
for (int j = s.y; j <= e.y; j++)
nowTop->x = i, nowTop++->y = j;
}
scanf("%*s %d %d %d %d %*s", &s.x, &s.y, &e.x, &e.y);
setMap(map, s, e, -2);
getchar();
while (gets(input) && input[1] != 'b')
{
st = getUInt(input, &s.x), st = getUInt(st, &s.y);
st = getUInt(st, &e.x), st = getUInt(st, &e.y);
setMap(map, s, e, -3);
}
ans = getAns(n - 1, m - 1, nowTop);
if (ans == -1)
puts("no path");
else
printf("%d", ans);
return 0;
}

e253: a + b problem

內容

#include <stdio.h>

int add(int a, int b) {
while(b) {
int ta = a _ b, tb = (a _ b) __ 1;
a = __, b = __;
}
return a;
}

int main() {
int a, b;
scanf("%d %d", &a, &b);
printf("%d", add(a, b));

return 0;
}

請把上述程式的'_'填完,讓 add()可以回傳 a+b 的結果。

一個'_'代表一個字元

不准使用+ - * /運算子


輸入

(本題無輸入)

(本題無輸入)

輸出

依照從上到下、從左到右的順序輸出填入的答案,並用空白隔開

ex:

/ * && gg cc

(答案被帕克吃掉了)


解題思路

int add(int a, int b)
{
while (b)
{
int ta = a ^ b, tb = (a & b) << 1;
a = ta, b = tb;
}
return a;
}

模擬加法。


完整程式碼

AC (19ms, 3.5MB)
#include <stdio.h>

int main()
{
puts("^ & << ta tb");
return 0;
}

a020: 身份證檢驗

內容

我國的身份證字號有底下這樣的規則,因此對於任意輸入的身份證字號可以有一些基本的判斷原則,請您來判斷一個身份證字號是否是正常的號碼(不代表確有此號、此人)。

(1) 英文代號以下表轉換成數字

A=10 台北市     J=18 新竹縣     S=26 高雄縣
B=11 台中市     K=19 苗栗縣     T=27 屏東縣
C=12 基隆市     L=20 台中縣     U=28 花蓮縣
D=13 台南市     M=21 南投縣     V=29 台東縣
E=14 高雄市     N=22 彰化縣     W=32 金門縣
F=15 台北縣     O=35 新竹市     X=30 澎湖縣
G=16 宜蘭縣     P=23 雲林縣     Y=31 陽明山
H=17 桃園縣     Q=24 嘉義縣     Z=33 連江縣
I=34 嘉義市     R=25 台南縣

(2) 英文轉成的數字, 個位數乘9再加上十位數的數字

(3) 各數字從右到左依次乘1、2、3、4....8

(4) 求出(2),(3) 及最後一碼的和

(5) (4)除 10 若整除,則為 real,否則為 fake

例: T112663836

2 + 7*9 + 1*8 + 1*7 + 2*6 + 6*5 + 6*4 + 3*3 + 8*2 + 3*1 + 6 = 180

除以 10 整除,因此為 real


輸入

一組身份證號碼

T112663836
S154287863

輸出

輸出 real or fake

real
fake


解題思路

建一個表去對應 A~Z 要對應的數字,接著依照規則算出結果後和 10 取餘判斷即可。


完整程式碼

AC (2ms, 60KB)
#include <stdio.h>

int sum, eng[26] = { 10 ,11 ,12 ,13 ,14 ,15 ,16 ,17 ,34 ,18 ,19 ,20 ,21
,22 ,35 ,23 ,24 ,25 ,26 ,27 ,28 ,29 ,32 ,30 ,31 ,33 };
char input[11];

int main()
{
while (gets(input) != NULL)
{
sum = eng[input[0] - 'A'] / 10 + (eng[input[0] - 'A'] % 10) * 9 + (input[8] - '0') + (input[9] - '0');
for (int i = 1; i < 8; i++)
sum += (input[i] - '0') * (9 - i);
if (sum % 10 == 0) puts("real");
else puts("fake");
}
return 0;
}

[模板] Heap(堆積)

模板

Heap 主結構

#define HEAPSIZ 1024

typedef struct
{
/* your data here */
}Node;

typedef struct
{
Node val[HEAPSIZ];
int last;
}Heap;

inline int cmp(register const Node* lhs, register const Node* rhs)
{
/* compare values here */
}

inline void swap(register Node* lhs, register Node* rhs)
{
Node tmp = *lhs;
*lhs = *rhs, * rhs = tmp;
}

void insNode(register Heap* heap, register Node val)
{
heap->val[++heap->_last] = val;
int now = heap->_last, base = now >> 1;
while (base && cmp(&heap->val[base], &heap->val[now]) < 0)
swap(&heap->val[now], &heap->val[base]), now >>= 1, base >>= 1;
}

void delNode(register Heap* heap)
{
swap(&heap->val[1], &heap->val[heap->_last--]);
int now = 1, derived = 2;
while (derived <= heap->_last)
{
if (cmp(&heap->val[derived], &heap->val[derived + 1]) < 0 && derived < heap->_last) derived++;
if (cmp(&heap->val[derived], &heap->val[now]) < 0) break;
swap(&heap->val[derived], &heap->val[now]), now <<= 1, derived <<= 1;
}
}

概述

本組模板為堆積模板,可以用來實現簡單的最大 / 最小堆。

結構

Node

本結構使用者自定義的資料,如果你只要使用編譯器預設的資料型態(int/double…),你可以將 Node 直接定義成該型態

typedef int Node;

這樣在新增節點時會比較方便。

Heap

本結構為堆積的主結構,使用他來宣告一個新的堆積,結構中的 _last 為保證結構正運作的必要參數,請不要任意更改它除非你清楚了解它的作用。

函式

cmp()

本函式為比較函式。

參數
lhs (const Node*)

本參數為比較資料的左項。

rhs (const Node*)

本參數為比較資料的右項。

回傳值

回傳比較後的結果。

insNode()

本函式會新增一筆資料至目標堆積。

參數
heap (Heap*)

本參數為目標堆積。

val (Node)

本參數為存入目標堆積的元素。

回傳值

本函式無回傳值。

delNode()

本函式會將目標堆積頂端的元素刪除。

參數
heap (Heap*)

本參數為目標堆積。

回傳值

本函式無回傳值。

其他

HEAPSIZ

此巨集用來定義元素的數量上限。

實例

#include <stdio.h>
#define HEAPSIZ 1024

typedef int Node;

typedef struct
{
Node val[HEAPSIZ];
int _last;
}Heap;

inline int cmp(register const Node* lhs, register const Node* rhs)
{
return *lhs - *rhs;
}

inline void swap(register Node* lhs, register Node* rhs)
{
Node tmp = *lhs;
*lhs = *rhs, * rhs = tmp;
}

void insNode(register Heap* heap, register Node val)
{
heap->val[++heap->_last] = val;
int now = heap->_last, base = now >> 1;
while (base && cmp(&heap->val[base], &heap->val[now]) < 0)
swap(&heap->val[now], &heap->val[base]), now >>= 1, base >>= 1;
}

Node delNode(register Heap* heap)
{
swap(&heap->val[1], &heap->val[heap->_last--]);
int now = 1, derived = 2;
while (derived <= heap->_last)
{
if (cmp(&heap->val[derived], &heap->val[derived + 1]) < 0 && derived < heap->_last) derived++;
if (cmp(&heap->val[derived], &heap->val[now]) < 0) break;
swap(&heap->val[derived], &heap->val[now]), now <<= 1, derived <<= 1;
}
}

int main()
{
Heap heap = { 0 };
insNode(&heap, 5);
insNode(&heap, 9);
insNode(&heap, 1);
insNode(&heap, 9);
insNode(&heap, 7);
printf("%d\n", heap.val[1]);
delNode(&heap);
printf("%d\n", heap.val[1]);
delNode(&heap);
printf("%d\n", heap.val[1]);
delNode(&heap);
printf("%d\n", heap.val[1]);
delNode(&heap);
printf("%d\n", heap.val[1]);
delNode(&heap);
return 0;
}
編譯運行結果

9
9
7
5
1

注意事項

  • 在區域變數裡宣告新的 Heap 時必須將其初始化

    Heap heap = { 0 };

    否則會因為存取違規而出現 runtime error;

  • 測試階段,謹慎使用。

更新紀錄

[模板] Queue(佇列)

模板

Queue 主結構

#include <stdio.h>
#define ENTSIZ 1024

typedef struct
{
struct Data
{
/* your data here */
};
struct Node* _next;
}Node;

typedef struct
{
Node* front, * last;
}Queue;

Node entries[ENTSIZ], * entry = entries, * freeBuk;

inline void push(register Queue* que, register Node val)
{
if (freeBuk)
{
Node* tmp = freeBuk->_next;
*freeBuk = val, freeBuk->_next = NULL;
if (que->last) que->last = que->last->_next = freeBuk;
else que->front = que->last = freeBuk;
freeBuk = tmp;
}
else
{
*entry = val;
if (que->last) que->last = que->last->_next = entry++;
else que->front = que->last = entry++;
}
}

inline Node pop(register Queue* que)
{
Node* tmp = que->front;
que->front = que->front->_next;
tmp->_next = freeBuk;
return *(freeBuk = tmp);
}

概述

本組模板為佇列模板,在已知元素數量上限時使用此模板可以省去動態宣告記憶體的時間,本模板可同時宣告並使用複數個 stack 和 queue,而只須保證資料結構相同且元素總數不超過定義上限即可。

結構

Node

本結構為資料的進入點,其中 struct Data 為供使用者存取資料的結構,_next 則是保證結構正運作的必要參數,請不要任意更改它除非你清楚了解它的作用。

Queue

本結構為佇列的主結構,使用他來宣告一個新的佇列,結構中的 front 和 last 指標分別恆指向該佇列的最前和最末端元素。

函式

push()

本函式會新增一筆資料至目標堆疊的最末端。

參數
stk (Queue*)

本參數為目標佇列。

val (Node)

本參數為存入目標佇列最末端的元素。

回傳值

本函式無回傳值。

pop()

本函式會將目標佇列最前端的資料取出並回傳該元素。

stk (Queue*)

本參數為目標佇列。

回傳值

本函式回傳目標佇列最前端的元素。

其他

ENTSIZ

此巨集用來定義元素的數量上限。

實例

#include <stdio.h>
#define ENTSIZ 1024

typedef struct
{
struct Data
{
int a, b;
};
struct Node* _next;
}Node;

typedef struct
{
Node* front, * last;
}Queue;

Node entries[ENTSIZ], * entry = entries, * freeBuk;

inline void push(register Queue* que, register Node val)
{
if (freeBuk)
{
Node* tmp = freeBuk->_next;
*freeBuk = val, freeBuk->_next = NULL;
if (que->last) que->last = que->last->_next = freeBuk;
else que->front = que->last = freeBuk;
freeBuk = tmp;
}
else
{
*entry = val;
if (que->last) que->last = que->last->_next = entry++;
else que->front = que->last = entry++;
}
}

inline Node pop(register Queue* que)
{
Node* tmp = que->front;
que->front = que->front->_next;
tmp->_next = freeBuk;
return *(freeBuk = tmp);
}

int main()
{
Queue queue = { 0 };
Node n = { 0, 1 };
push(&queue, n);
n.b = 2;
push(&queue, n);
push(&queue, pop(&queue));
n.b = 3;
push(&queue, n);
n = pop(&queue);
printf("%d,%d\n", n.a, n.b);
printf("%d,%d\n", queue.front->a, queue.front->b);
printf("%d,%d\n", queue.last->a, queue.last->b);
return 0;
}
編譯運行結果

0,2
0,1
0,3

注意事項

  • 如果自定義資料較大,你應該將存取方式改為使用指標,並透過映射的方式存取資料來減少記憶體的操作次數。

  • 在區域變數裡宣告新的 Queue 時必須將其初始化

    Queue queue = { 0 };

    否則會因為存取違規而出現 runtime error;

  • 如果要同時使用 Stack 和 Queue,請將兩者的 push() 和 pop() 函式用不同名字區隔開來。

  • 測試階段,謹慎使用。

更新紀錄

[模板] Stack(堆疊)

模板

Stack 主結構

#define ENTSIZ 1024

typedef struct
{
struct Data
{
/* your data here */
};
struct Node* _next;
}Node;

typedef struct
{
Node* top;
}Stack;

Node entries[ENTSIZ], * entry = entries, * freeBuk;

inline void push(register Stack* stk, register Node val)
{
if (freeBuk)
{
Node* tmp = freeBuk->_next;
*freeBuk = val, freeBuk->_next = stk->top;
stk->top = freeBuk;
freeBuk = tmp;
}
else
{
*entry = val, entry->_next = stk->top;
stk->top = entry++;
}
}

inline Node pop(register Stack* stk)
{
Node* tmp = stk->top;
stk->top = stk->top->_next;
tmp->_next = freeBuk;
return *(freeBuk = tmp);
}

概述

本組模板為堆疊模板,在已知元素數量上限時使用此模板可以省去動態宣告記憶體的時間,本模板可同時宣告並使用複數個 stack 和 queue,而只須保證資料結構相同且元素總數不超過定義上限即可。

結構

Node

本結構為資料的進入點,其中 struct Data 為供使用者存取資料的結構,_next 則是保證結構正運作的必要參數,請不要任意更改它除非你清楚了解它的作用。

Stack

本結構為堆疊的主結構,使用他來宣告一個新的堆疊,結構中的 top 指標恆指向該堆疊最頂端的元素。

函式

push()

本函式會新增一筆資料至目標堆疊的頂端。

參數
stk (Stack*)

本參數為目標堆疊。

val (Node)

本參數為存入目標堆疊頂層的元素。

回傳值

本函式無回傳值。

pop()

本函式會將目標堆疊頂層的資料取出並回傳該元素。

stk (Stack*)

本參數為目標堆疊。

回傳值

本函式回傳目標堆疊頂層元素。

其他

ENTSIZ

此巨集用來定義元素的數量上限。

實例

#include <stdio.h>
#define ENTSIZ 1024

typedef struct
{
struct Data
{
int a, b;
};
struct Node* _next;
}Node;

typedef struct
{
Node* top;
}Stack;

Node entries[ENTSIZ], * entry = entries, * freeBuk;

inline void push(register Stack* stk, register Node val)
{
if (freeBuk)
{
Node* tmp = freeBuk->_next;
*freeBuk = val, freeBuk->_next = stk->top;
stk->top = freeBuk;
freeBuk = tmp;
}
else
{
*entry = val, entry->_next = stk->top;
stk->top = entry++;
}
}

inline Node pop(register Stack* stk)
{
Node* tmp = stk->top;
stk->top = stk->top->_next;
tmp->_next = freeBuk;
return *(freeBuk = tmp);
}

int main()
{
Stack stack1 = { 0 }, stack2 = { 0 };
Node n = { 0, 1 };
push(&stack1, n);
n.b = 2;
push(&stack2, n);
push(&stack1, pop(&stack2));
n = pop(&stack1);
printf("%d,%d\n", n.a, n.b);
printf("%d,%d\n", stack1.top->a, stack1.top->b);
n = pop(&stack1);
printf("%d,%d", n.a, n.b);
return 0;
}
編譯運行結果

0,2
0,1
0,1

注意事項

  • 如果自定義資料較大,你應該將存取方式改為使用指標,並透過映射的方式存取資料來減少記憶體的操作次數。
  • 如果要同時使用 Stack 和 Queue,請將兩者的 push() 和 pop() 函式用不同名字區隔開來。
  • 測試階段,謹慎使用。

更新紀錄

2019-9-21
  • 調整運作結構使其能和 Queue 混用同一塊記憶體 (在資料結構相同的情況)
  • 優化 pop(),現在比之前少執行一行命令。

模板列表

字串處理

字串取值

本組模板為字串轉數字模板,使用此模板取代 sscanf() 可以大幅減少從字串取得數組的時間,此外本模板內建指針偏移,可以節省使用 strlen() 偏移指針的開銷。

字串賦值

本組模板為整數轉字串模板,使用此模板取代 sprintf() 可以大幅將整數打印到字串的時間,此外本模板內建指針偏移,可以節省使用 strlen() 偏移指針的開銷。

IO 優化

輸入優化

本組模板為輸入優化模板,使用此模板取代 scanf() 可以大幅減少從資料流取得數組的時間。

輸出優化

本組模板為輸出優化模板,使用此模板取代 printf() 可以大幅印出資料的時間。

資料結構

Stack(堆疊)

本組模板為堆疊模板,在已知元素數量上限時使用此模板可以省去動態宣告記憶體的時間。

Queue(佇列)

本組模板為佇列模板,在已知元素數量上限時使用此模板可以省去動態宣告記憶體的時間。

Win10 改變標題列顏色

重灌完電腦發現以前用的登錄檔不見了,google 又充滿了只能改聚焦視窗標題列顏色的複製文,嚇得我趕緊寫一篇文章備份壓壓驚。

步驟

  1. Win + R 輸入 regedit 開啟登錄檔編輯程式

  2. 移動至 HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM

  3. 找到 AccentColor 和 AccentColorInactive,這兩個值分別代表聚焦視窗和非聚焦視窗的標題列顏色,如果沒有就新增一個同名的 dword。

  4. 修改數值成喜歡的顏色即可。

經典藍登錄檔

這裡提供一個回復成 WIN10 早期標題列皆為藍色的登錄檔,將以下文字複製進記事本並將副檔名儲存為.reg,雙擊該登錄檔(.reg)即可。

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM]
"Composition"=dword:00000001
"ColorizationColor"=dword:c40078d7
"ColorizationColorBalance"=dword:00000059
"ColorizationAfterglow"=dword:c40078d7
"ColorizationAfterglowBalance"=dword:0000000a
"ColorizationBlurBalance"=dword:00000001
"ColorizationGlassAttribute"=dword:00000001
"AccentColor"=dword:ffd77800
"ColorPrevalence"=dword:00000001
"EnableAeroPeek"=dword:00000001
"EnableWindowColorization"=dword:00000001
"AccentColorInactive"=dword:ffd77800

參考資料:https://www.winhelponline.com/blog/change-inactive-title-bar-color-windows-10/

b237: CSAPC'09 迷宮任務

內容

在一個 n 乘以 m 大小的迷宮當中,你必須依序執行很多項搬東西的任務。每一個任務都有起始點 A 以及終點 B 。行動的時候如果手上沒有搬東西,就不會耗費任何體力,但是如果手上有東西,那麼耗費的體力就是以移動「一格」消耗一單位的體力來計算。而所謂移動「一格」是指從目前迷宮中的位置往東、南、西、北四個方向前進一步的長度。有趣的是,經過你的縝密觀察,儘管迷宮內部相當複雜,它一定會遵守「右手定則」,也就是說,只要摸著右手邊的牆壁不斷地往前行走,終究可以踏遍迷宮中的每一塊空地,並且連所有空地周圍看得到的圍牆部分都被你摸過了。聰明的你在執行每一個任務的時候,都會挑選最短路徑搬運指定物品。請問執行完所有任務以後你所需要耗費的最少體力總和是多少?


輸入

輸入的第一行包含三個正整數 n, m, Q 。接下來我們用 n 列(rows)每一列以 m 個字元來表示迷宮。迷宮內部標註 ‘.’ 的部份是空地,標記 ‘#’ 的部份是牆。迷宮最外圍一定都是牆壁。接下來有 Q 列分別代表 Q 項任務的起點和終點,每一列包含四個整數 x, y, z, w ,代表每一項任務從 (x, y) 出發,並且在 (z, w) 結束。注意到迷宮的編號方式以左上角為 (0, 0) ,右下角為 (n-1, m-1) 。你可以假設輸入的所有座標都會是空地的座標。 迷宮裡面不會有 2x2 的空地出現。 (edit: 11/16 10:17am)

10 10 6
##########
#......#.#
#####.#..#
#####...##
###.#.#.##
###.#.#..#
##...#..##
##.#..#.##
#...#....#
##########
1 1 1 1
3 6 1 3
3 6 6 3
2 8 2 5
2 5 2 8
7 4 6 2

輸出

請輸出依序執行完所有任務後所需要耗費的最小體力。

30


解題思路

因為題目保證"迷宮裡面不會有 2x2 的空地出現"所以可以將這個迷宮視為一個樹,而在一個樹狀結構中點到點的距離會是

點 a 到 LCA[1](a,b) 的距離 + 點 b 到 LCA(a,b) 的距離

而這兩者相加亦會等於

點 a 到 根的距離 + 點 B 到 根的距離 - LCA(a,b)到根的距離 * 2

所以說只要知道 a , b 和 LCA(a,b) 與根的相對深度就能求出答案了。

但由於本題詢問量很大,如果每次詢問都一個一個找時間複雜度為

O(q*(a+b))) q = 詢問數, a(b) = 各詢問中點 a(b) 到 LCA(a,b) 的距離

這樣會花費太多時間,所以改用離線 Tarjan 演算法配合並查集先找出所有點對點的 LCA ,之後只需要 O(1) 的時間就可以完成查詢。

但是本題範圍較大,建立 LCA 表記憶體會不夠用,所以改成先把所有查詢紀錄起來,之後再一邊遍歷節點一邊計算答案,遍歷結束後輸出答案即可。

  • 本題各測資點皆只有一筆測資,輸入量不小
  • 程式碼中 tarjan() 不需判斷是否被遍歷過是因為在建立的時候已經將多餘的情況給過濾掉了。

註解

  1. LCA = Lowest Common Ancestor = 最低共同祖先

參考資料


完整程式碼

正常版

AC (22ms, 3.6MB)
#include <stdio.h>
#define MAX 255
#define MAX2 62500

typedef struct
{
int val[4], * top;
}Stack;

typedef struct
{
int val, next;
}Query;

Stack list[MAX2];
Query qList[50005];
int map[MAX][MAX], parent[MAX2], query[MAX2], depth[MAX2],sum, len, qTop;
char input[MAX][MAX];

void dfs(int i, int j, int dep)
{
int idx = ++len;
parent[idx] = map[i][j] = idx;
depth[idx] = dep;
list[idx].top = list[idx].val;
if (input[i + 1][j] == '.' && !map[i + 1][j])
dfs(i + 1, j, dep + 1), * list[idx].top++ = map[i + 1][j];
if (input[i - 1][j] == '.' && !map[i - 1][j])
dfs(i - 1, j, dep + 1), * list[idx].top++ = map[i - 1][j];
if (input[i][j + 1] == '.' && !map[i][j + 1])
dfs(i, j + 1, dep + 1), * list[idx].top++ = map[i][j + 1];
if (input[i][j - 1] == '.' && !map[i][j - 1])
dfs(i, j - 1, dep + 1), * list[idx].top++ = map[i][j - 1];
}

inline void setDepth(int n, int m)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (input[i][j] == '.')
{
dfs(i, j, 0);
return;
}
}
}
}

inline void addQuery(int from, int to)
{
qTop++;
qList[qTop].val = to;
qList[qTop].next = query[from];
query[from] = qTop;
}

int find(int i)
{
return i == parent[i] ? i : (parent[i] = find(parent[i]));
}

void tarjan(int i)
{
for (Query* j = &qList[query[i]]; j->val; j = &qList[j->next])
sum += depth[i] + depth[j->val] - (depth[find(j->val)] << 1);
for (int* j = list[i].val; j < list[i].top; j++)
{
tarjan(*j);
parent[*j] = i;
}
}

int main()
{
int n, m, q, sX, sY, eX, eY, s, e;
scanf(" %d %d %d", &n, &m, &q);
getchar();
for (int i = 0, idx = 0; i < n; i++)
gets(input[i]);
setDepth(n, m);
for (int i = 0; i < q; i++)
{
scanf(" %d %d %d %d", &sX, &sY, &eX, &eY);
s = map[sX][sY], e = map[eX][eY];
if (s < e)
addQuery(e, s);
else
addQuery(s, e);
}
tarjan(1);
printf("%d\n", sum);
return 0;
}

輸入優化版

AC (8ms, 4.3MB)
#include <stdio.h>
#define MAX 255
#define MAX2 62500
#define BUFSIZ 1048576

typedef struct
{
int val[4], * top;
}Stack;

typedef struct
{
int val, next;
}Query;

Stack list[MAX2];
Query qList[50005];
int map[MAX][MAX], parent[MAX2], query[MAX2], depth[MAX2], sum, len, qTop;
char input[MAX][MAX];

inline char readChar()
{
static char buffer[BUFSIZ], * now = buffer + BUFSIZ, * end = buffer + BUFSIZ;
if (now == end)
{
if (end < buffer + BUFSIZ)
return EOF;
end = (buffer + fread(buffer, 1, BUFSIZ, stdin));
now = buffer;
}
return *now++;
}

inline char readString(register unsigned char* dst)
{
register char ch;
while ((ch = readChar()) <= ' ')
if (ch == EOF) return 0;
*dst++ = ch;
while ((ch = readChar()) > ' ')
* dst++ = ch;
return 1;
}

inline char readUInt(register unsigned int* dst)
{
register char ch;
while ((ch = readChar()) < '0')
if (ch == EOF) return 0;
*dst = ch ^ '0';
while ((ch = readChar()) >= '0')
* dst = (*dst << 3) + (*dst << 1) + (ch ^ '0');
return 1;
}

void dfs(int i, int j, int dep)
{
int idx = ++len;
parent[idx] = map[i][j] = idx;
depth[idx] = dep;
list[idx].top = list[idx].val;
if (input[i + 1][j] == '.' && !map[i + 1][j])
dfs(i + 1, j, dep + 1), * list[idx].top++ = map[i + 1][j];
if (input[i - 1][j] == '.' && !map[i - 1][j])
dfs(i - 1, j, dep + 1), * list[idx].top++ = map[i - 1][j];
if (input[i][j + 1] == '.' && !map[i][j + 1])
dfs(i, j + 1, dep + 1), * list[idx].top++ = map[i][j + 1];
if (input[i][j - 1] == '.' && !map[i][j - 1])
dfs(i, j - 1, dep + 1), * list[idx].top++ = map[i][j - 1];
}

inline void setDepth(int n, int m)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (input[i][j] == '.')
{
dfs(i, j, 0);
return;
}
}
}
}

inline void addQuery(int from, int to)
{
qTop++;
qList[qTop].val = to;
qList[qTop].next = query[from];
query[from] = qTop;
}

int find(int i)
{
return i == parent[i] ? i : (parent[i] = find(parent[i]));
}

void tarjan(int i)
{
for (Query* j = &qList[query[i]]; j->val; j = &qList[j->next])
sum += depth[i] + depth[j->val] - (depth[find(j->val)] << 1);
for (int* j = list[i].val; j < list[i].top; j++)
{
tarjan(*j);
parent[*j] = i;
}
}

int main()
{
int n, m, q, sX, sY, eX, eY, s, e;
scanf(" %d %d %d", &n, &m, &q);
for (int i = 0, idx = 0; i < n; i++)
readString(input[i]);
setDepth(n, m);
for (int i = 0; i < q; i++)
{
readUInt(&sX), readUInt(&sY);
readUInt(&eX), readUInt(&eY);
s = map[sX][sY], e = map[eX][eY];
if (s < e)
addQuery(e, s);
else
addQuery(s, e);
}
tarjan(1);
printf("%d\n", sum);
return 0;
}

b519: 撲克牌遊戲-商競103

內容

許多人常喜歡玩撲克牌,一副牌共有 52 張牌,有四種花色:黑桃、紅桃、方塊、和梅花。在撲克牌的玩法中,A 可作 1 點或 14 點,而 2-10 則為該牌之點數,另外 J、Q、K 分別為 11、12、13 點。在測試檔案中,每位玩家只會分到 5 張牌。下表將 52 張牌分別對應到數字 1~52,在測試檔案中,將以下表的數字代表某張牌。

點數
花色  A  2  3  4  5  6  7  8  9 10  J  Q  K
黑桃  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

五張牌的相關的牌型如下:
「同花順」為同花色五張連續數字,相同花色的「順子」,得分 7 分;
「四條」為四張同數字的牌,外加任一單張的五張牌,得分 6 分;
「葫蘆」為三張同數字,另兩張同數字的牌;一個「一對」和「三條」所組成的五張牌;得分 5 分;
「順子」為五張數字連續的牌,數字各差1點的連續牌,從 A-2-3-4-5(1-2-3-4-5),到 10-J-Q-K-A(有 10-11-12-13-14,但沒有 J-Q-K-A-2),得分 4 分;
「三條」五張牌中包含三張同數字的牌,得分 3 分;
「兩對」五張牌中包含兩對兩同數字的牌,但不是四張相同數字的牌(非四條),得分 2 分;
「一對」五張牌中包含只有兩張同數字的牌,得分 1 分;
「雜牌」指不屬於以上任何一種組合,得分 0 分。
本題目的是判斷手上的五張牌是屬於以上那一種牌型,以得分代替牌型。


輸入

每個輸入資料含多個玩家取得的撲克牌資料,在檔案 in1.txt 和 in2.txt 中,每個玩家分別各使用一副牌,第 1 列的數字 n 代表有幾筆玩家資料要測試, ,第二列起為測試資料,之後每列為每筆的測試資料,代表每個玩家拿到的五張撲克牌,五張牌所代表的數間以空白隔開,而空白不限定一個。如上表所示,每張牌以一個數字(1~52)代表,例如:以 18 代表紅桃 5。

6
3 44 4 19 7
6 12 1 32 45
26 25 2 38 39
15 18 2 28 41
14 21 22 23 24
1 13 26 27 39

輸出

按照每個玩家手上的 5 張牌,判斷每個玩家手上的五張牌是那一種牌型;以得分代替牌型。

5 張牌的數字所對應到的牌型分別如下:
3 44 4 19 7 -> 順子;得分 4 分
6 12 1 32 45 -> 三條;得分 3 分
26 25 2 38 39 -> 兩對;得分 2 分
15 18 2 28 41 -> 四條;得分 6 分
14 21 22 23 24 -> 雜牌;得分 0 分
1 13 26 27 39 -> 葫蘆;得分 5 分

4
3
2
6
0
5


解題思路

先將數字分別除上和取於 13 得到該牌的花色和數字並記錄下來。

再來分成兩部分解,第一部分判斷對子類,也就數字有是重複的部分,如果有產生任一種對子(含三條葫蘆等) 就必定不會產生順子類,因為順子要 5 張都不同。

第二部分判斷順子類,如果上面沒有產生任何對子類,那就只可能是同花順、順子或雜牌,再進行一次判斷即可。


完整程式碼

AC (2ms, 104KB)
#include <stdio.h>
#include <stdlib.h>

typedef struct
{
int suit, num;
}Poker;

cmp(const Poker* lhs, const Poker* rhs)
{
return lhs->num - rhs->num;
}

int main()
{
Poker list[5];
int kase, tmp, count, s, n;
scanf(" %d", &kase);
while (kase--)
{
int pair[5] = { 0 };
for (int i = 0; i < 5; i++)
{
scanf(" %d", &tmp);
count = 1, tmp--;
list[i].suit = tmp / 13;
list[i].num = tmp % 13;
for (int j = i - 1; ~j; j--)
{
if (list[j].num == list[i].num)
count++;
}
pair[count]++, pair[count - 1]--;
}
if (pair[4]) puts("6");
else if (pair[3])
{
if (pair[2]) puts("5");
else puts("3");
}
else if (pair[2])
{
if (pair[2] == 2) puts("2");
else puts("1");
}
else
{
qsort(list, 5, sizeof(Poker), cmp);
s = n = 1;
for (int i = 1; i < 4; i++)
{
if (list[i].num - 1 != list[i - 1].num)
{
n = 0;
break;
}
if (list[i].suit != list[i - 1].suit)
s = 0;
}
if (n)
{
if (s) puts("7");
else puts("4");
}
else puts("0");
}
}
return 0;
}
12327