描述
祖瑪是一款曾經風靡全球的游戲,其玩法是:在一條軌道上初始排列着若干個彩色珠子,其中任意三個相鄰的珠子不會完全同色。此后,你可以發射珠子到軌 道上並加入原有序列中。一旦有三個或更多同色的珠子變成相鄰,它們就會立即消失。這類消除現象可能會連鎖式發生,其間你將暫時不能發射珠子。
開發商最近准備為玩家寫一個游戲過程的回放工具。他們已經在游戲內完成了過程記錄的功能,而回放功能的實現則委托你來完成。
游戲過程的記錄中,首先是軌道上初始的珠子序列,然后是玩家接下來所做的一系列操作。你的任務是,在各次操作之后及時計算出新的珠子序列。
輸入
第一行是一個由大寫字母'A'~'Z'組成的字符串,表示軌道上初始的珠子序列,不同的字母表示不同的顏色。
第二行是一個數字n,表示整個回放過程共有n次操作。
接下來的n行依次對應於各次操作。每次操作由一個數字k和一個大寫字母Σ描述,以空格分隔。其中,Σ為新珠子的顏色。若插入前共有m顆珠子,則k ∈ [0, m]表示新珠子嵌入之后(尚未發生消除之前)在軌道上的位序。
輸出
輸出共n行,依次給出各次操作(及可能隨即發生的消除現象)之后軌道上的珠子序列。
如果軌道上已沒有珠子,則以“-”表示。
輸入樣例
ACCBA
5
1 B
0 A
2 B
4 C
0 A
輸出樣例
ABCCBA
AABCCBA
AABBCCBA
-
A
限制
0 ≤ n ≤ 10^4
0 ≤ 初始珠子數量 ≤ 10^4
時間:2s,內存:256MB
提示
列表
【Solution】
先貼源碼:
1 #include <stdio.h> 2 #include "string.h" 3 #include <stdlib.h> 4 5 typedef char ElemType; 6 typedef struct node 7 { 8 ElemType data; 9 struct node *next; 10 struct node *front; 11 }List, *pList; 12 13 pList pHead = (pList)malloc(sizeof(List)); 14 pList pTail = (pList)malloc(sizeof(List)); 15 16 void creat(char *a, int n) 17 { 18 int i; 19 pList pt = pHead; 20 21 pTail->front = pHead; 22 pTail->next = NULL; 23 pHead->next = pTail; 24 pHead->front = NULL; 25 pHead->data = pTail->data = '-'; 26 27 for (i = 0; i < n; i++) 28 { 29 pList pNew = (pList)malloc(sizeof(List)); 30 pNew->data = a[i]; 31 pNew->front = pt; 32 pNew->next = pt->next; 33 pt->next->front = pNew; 34 pt->next = pNew; 35 pt = pNew; 36 } 37 } 38 39 void insert(int m, char ch) 40 { 41 int i = -1; 42 pList pt = pHead, pNew = (pList)malloc(sizeof(List)); 43 44 while (i++ < m) pt = pt->next; 45 46 pNew->data = ch; 47 pNew->next = pt; 48 pNew->front = pt->front; 49 pt->front->next = pNew; 50 pt->front = pNew; 51 } 52 53 void del(int m) 54 { 55 pList p1 = NULL, p2 = NULL, p3 = NULL, p4 = NULL, pt = pHead; 56 pList begin = pHead, end = pTail; 57 bool boo = true; 58 int repeat, i = -1; 59 60 // find position 61 while (i++ < m - 2) pt = pt->next; 62 63 //init for 'begin' and 'end' 64 begin = pt; end = pt; i = 0; 65 while (i++ < 4 && end->next != pTail) end = end->next; 66 67 while (boo && pt != pTail) 68 { 69 boo = false; repeat = 1; 70 while (pt != end) 71 { 72 pt = pt->next; 73 74 if (pt->front->data == pt->data) repeat++; 75 else repeat = 1; 76 77 if (repeat == 3) 78 { 79 boo = true; 80 if (pt->data == pt->next->data) 81 { 82 repeat++; 83 pt = pt->next; 84 } 85 86 if (repeat == 3) 87 { 88 p3 = pt; p2 = p3->front; p1 = p2->front; 89 p1->front->next = p3->next; 90 p3->next->front = p1->front; 91 pt = pt->next; 92 delete p1; delete p2; delete p3; 93 } 94 else 95 { 96 p4 = pt; p3 = p4->front; p2 = p3->front; p1 = p2->front; 97 p1->front->next = p4->next; 98 p4->next->front = p1->front; 99 pt = pt->next; 100 delete p1; delete p2; delete p3; delete p4; 101 } 102 103 break; 104 } 105 } 106 107 if (boo && pt != pTail) 108 { 109 begin = pt; i = 0; 110 while (i++ < 2 && begin->front != pHead) begin = begin->front; 111 end = pt; i = 0; 112 if (i++ < 1 && end->next != pTail) end = end->next; 113 pt = begin; 114 } 115 } 116 } 117 118 void show() 119 { 120 pList pt = pHead->next; 121 122 if (pt == pTail) printf("-"); 123 else 124 { 125 while (pt->next != NULL) 126 { 127 printf("%c", pt->data); 128 pt = pt->next; 129 } 130 } 131 132 printf("\n"); 133 } 134 135 int main(void) 136 { 137 char a[10005]; 138 int n, k; 139 pList pHead = NULL; 140 141 gets(a); 142 scanf("%d\n", &n); 143 144 creat(a, strlen(a)); 145 146 for (k = 0; k < n; k++) 147 { 148 int m; 149 char ch; 150 151 scanf("%d ", &m); 152 do 153 { 154 ch = getchar(); 155 } while (!((ch >= 'A') && (ch <= 'Z'))); 156 157 // insert ch 158 insert(m, ch); 159 160 // delete all 3-same block, making it the right string 161 del(m); 162 163 // print the string 164 show(); 165 } 166 167 return 0; 168 }
可以過 Tsinghua OJ 95%的數據,最后一個點超時,聽說要把緩存區調大或者用fread讀取數據才能過,暫時無解。
這一類題屬於模擬題,也就是按照題意一步一步模擬操作即可,對現實事物的合理抽象以及模擬操作的效率是解決問題的關鍵。
要注意的幾個點:
1、注意列表與向量數據結構的差別。向量可以直接 “循秩訪問(call-by-rank)”,所以對於查找操作是O(1)的,插入和刪除都是O(n)的,對於二分查找等這樣十分依賴於“秩”的算法很重要;而列表是“循位置訪問(call-by-position)”,所以對於 插入、刪除都是O(1)的操作,而查找操作是O(n)的。要充分注意它們的特點。實際上,對於這道題,雖然提示里寫了“列表”,由於每次都要遍歷輸出和查找,已經是O(n)的了,不見得比用向量做會快多少。
2、列表處理的一個特別好的小技巧:在列表的前面和后面各放置一個哨兵,如果把列表比作一條“繩子”,那么就相當於兩頭各放置一個手抓的地方,這樣無論列表內部會有哪些動態操作(即改變本身結構的操作,比如插入刪除,而相應的查找等不改變自己結構的操作則稱為靜態操作),都可以從一而終地從兩頭把列表給“拉”出來。好處在於,有很多需要考慮邊界情況的問題可以自動化的化為一般化的處理,也不必因為可能的刪除或插入操作不斷更新列表頭。之前做這道題並未這樣考慮的結果就是代碼里面各種判斷是否為NULL以防止邊界情況出錯,有了哨兵這樣的判斷很多時候可以一般化處理,判斷大大減少。同時也防止了越界錯誤的發生。
3、像Python那樣,總是從一而終地考慮[a, b)這樣的左閉右開區間是很有必要的,即區間左界樁總是被問題范圍所包含,而右界樁在當前狀態則不被包含。它可以大大的減少你思考問題的復雜度。遵循統一的標准也減少了犯錯的可能。
4、同樣,對於列表所對應的具體的數據結構鏈表,總是要尤其注意邊界情況。要注意的是:抽象數據類型是對數據結構更高層次的抽象。它是一種抽象定義,表現為邏輯上的特征和一些基本操作及語義,並不涉及數據的具體存儲方式。比如向量和列表。最常見的對應於這兩種抽象數據類型的具體數據類型也就是 數組 和 鏈表了。抽象有利於定義統一的借口和規范以便更一般化的歸納、使用和處理。
5、對於這道題,自己的一點小優化:
考慮到每次需要刪除的部分一定包含插入點,所以每次刪除的時候就直接定位到插入點以及它附近。
假設 插入點是k,第一次則考察k-2~k+2這五個點,
假設 有刪除操作,設刪除區段的后繼元素為m,
之后考察 m-2 ~ m+1這四個點。
重復以上兩步直到掃描這個區間不再有刪除操作。
這樣就不需要每次都掃描整個列表來判斷需不需要刪除了。
這一切都基於,列表的“局部切除手術”只可能發生在插入點附近,並且一定包含插入點。
6、另外:判等否操作比判大小關系操作效率要高;
【AC版代碼】
對於這道題,由於每次都要輸出 n 次,每次輸出都要遍歷一遍列表,每次都要調用I/O口,把輸出內容壓到緩存區,然后打印出來,這樣其實消耗了大量的時間。
最后,我考慮不要每操作一次就輸出一次,把幾次操作的內容存到一個字符串,到達一定的上限再輸出,然后AC了。
改進后的源代碼:
1 #include <stdio.h> 2 #include "string.h" 3 #include <stdlib.h> 4 5 #define Len 200000000 6 #define Up (Len*3/4) 7 8 typedef char ElemType; 9 typedef struct node 10 { 11 ElemType data; 12 struct node *next; 13 struct node *front; 14 }List, *pList; 15 16 pList pHead = (pList)malloc(sizeof(List)); 17 pList pTail = (pList)malloc(sizeof(List)); 18 19 char ans[Len + 5]; 20 int forprt = 0; 21 22 void creat(char *a, int n) 23 { 24 int i; 25 pList pt = pHead; 26 27 pTail->front = pHead; 28 pTail->next = NULL; 29 pHead->next = pTail; 30 pHead->front = NULL; 31 pHead->data = pTail->data = '-'; 32 33 for (i = 0; i < n; i++) 34 { 35 pList pNew = (pList)malloc(sizeof(List)); 36 pNew->data = a[i]; 37 pNew->front = pt; 38 pNew->next = pt->next; 39 pt->next->front = pNew; 40 pt->next = pNew; 41 pt = pNew; 42 } 43 } 44 45 void insert(int m, char ch) 46 { 47 int i = -1; 48 pList pt = pHead, pNew = (pList)malloc(sizeof(List)); 49 50 while (i++ < m) pt = pt->next; 51 52 pNew->data = ch; 53 pNew->next = pt; 54 pNew->front = pt->front; 55 pt->front->next = pNew; 56 pt->front = pNew; 57 } 58 59 void del(int m) 60 { 61 pList p1 = NULL, p2 = NULL, p3 = NULL, p4 = NULL, pt = pHead; 62 pList begin = pHead, end = pTail; 63 bool boo = true; 64 int repeat, i = -1; 65 66 // find position 67 while (i++ < m - 2) pt = pt->next; 68 69 //init for 'begin' and 'end' 70 begin = pt; end = pt; i = 0; 71 while (i++ < 4 && end->next != pTail) end = end->next; 72 73 while (boo && pt != pTail) 74 { 75 boo = false; repeat = 1; 76 while (pt != end) 77 { 78 pt = pt->next; 79 80 if (pt->front->data == pt->data) repeat++; 81 else repeat = 1; 82 83 if (repeat == 3) 84 { 85 boo = true; 86 if (pt->data == pt->next->data) 87 { 88 repeat++; 89 pt = pt->next; 90 } 91 92 if (repeat == 3) 93 { 94 p3 = pt; p2 = p3->front; p1 = p2->front; 95 p1->front->next = p3->next; 96 p3->next->front = p1->front; 97 pt = pt->next; 98 delete p1; delete p2; delete p3; 99 } 100 else 101 { 102 p4 = pt; p3 = p4->front; p2 = p3->front; p1 = p2->front; 103 p1->front->next = p4->next; 104 p4->next->front = p1->front; 105 pt = pt->next; 106 delete p1; delete p2; delete p3; delete p4; 107 } 108 109 break; 110 } 111 } 112 113 if (boo && pt != pTail) 114 { 115 begin = pt; i = 0; 116 while (i++ < 2 && begin->front != pHead) begin = begin->front; 117 end = pt; i = 0; 118 if (i++ < 1 && end->next != pTail) end = end->next; 119 pt = begin; 120 } 121 } 122 } 123 124 void show(bool boo) 125 { 126 pList pt = pHead->next; 127 128 if (pt == pTail) ans[forprt++] = '-'; 129 else 130 { 131 while (pt->next != NULL) 132 { 133 ans[forprt++] = pt->data; 134 pt = pt->next; 135 } 136 } 137 138 ans[forprt++] = '\n'; 139 140 if (forprt >= Up || boo) 141 { 142 ans[forprt] = '\0'; 143 printf("%s", ans); 144 forprt = 0; 145 } 146 } 147 148 int main(void) 149 { 150 char a[10005]; 151 int n, k; 152 pList pHead = NULL; 153 154 gets(a); 155 scanf("%d\n", &n); 156 157 creat(a, strlen(a)); 158 159 for (k = 0; k < n; k++) 160 { 161 int m; 162 char ch; 163 164 scanf("%d ", &m); 165 do 166 { 167 ch = getchar(); 168 } while (!((ch >= 'A') && (ch <= 'Z'))); 169 170 // insert ch 171 insert(m, ch); 172 173 // delete all 3-same block, making it the right string 174 del(m); 175 176 // print the string 177 show(k == n - 1 ? true : false); 178 } 179 180 return 0; 181 }
需要注意的點:
1、注意常量 Len 和 Up 的關系,一定不能把 Up 簡單地設置為 Len。因為如果那樣,可能某次輸出前的最后一個操作后的字符串加在原來待輸出的大字符串后面,還來不及判斷是否超過上限就已經數組下標越界了。
2、Len 已經不能再大了,再大超空間了。