【Tsinghua OJ】祖瑪(Zuma)問題


描述

祖瑪是一款曾經風靡全球的游戲,其玩法是:在一條軌道上初始排列着若干個彩色珠子,其中任意三個相鄰的珠子不會完全同色。此后,你可以發射珠子到軌 道上並加入原有序列中。一旦有三個或更多同色的珠子變成相鄰,它們就會立即消失。這類消除現象可能會連鎖式發生,其間你將暫時不能發射珠子。

開發商最近准備為玩家寫一個游戲過程的回放工具。他們已經在游戲內完成了過程記錄的功能,而回放功能的實現則委托你來完成。

游戲過程的記錄中,首先是軌道上初始的珠子序列,然后是玩家接下來所做的一系列操作。你的任務是,在各次操作之后及時計算出新的珠子序列。

輸入

第一行是一個由大寫字母'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 已經不能再大了,再大超空間了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM