最近看到牛課網美團一個編程競賽,想着做做看,結果一寫就是兩天。。真是寫不動了啊。話不多說,下面開始我的題解。
題目大致還是比較考察思維和代碼能力(因為自己代碼能力較弱,才會覺得比較考察代碼能力吧= =!),難度由簡到難變化也比較適中,有簽到題、有算法實現,當然也有稍稍一點代碼量的題。感謝美團點評,提供一套合適的題目~
音樂研究
題目描述
具體地說,就是在第二段音頻中找到一個長度和第一段音頻相等且是連續的子序列,使得它們的 difference 最小。兩段等長音頻的 difference 定義為:
difference = SUM(a[i] - b[i])2 (1 ≤ i ≤ n),其中SUM()表示求和
其中 n 表示序列長度,a[i], b[i]分別表示兩段音頻的音高。現在袋鼠先生想要知道,difference的最小值是多少?數據保證第一段音頻的長度小於等於第二段音頻的長度。
輸入描述:
第一行一個整數n(1 ≤ n ≤ 1000),表示第一段音頻的長度。
第二行n個整數表示第一段音頻的音高(0 ≤ 音高 ≤ 1000)。
第三行一個整數m(1 ≤ n ≤ m ≤ 1000),表示第二段音頻的長度。
第四行m個整數表示第二段音頻的音高(0 ≤ 音高 ≤ 1000)。
輸出描述:
輸出difference的最小值
輸入例子:
2 1 2 4 3 1 2 4
輸出例子:
0
題解:
簽到題,n*m<1e6,直接暴力枚舉第二個數組的起點即可,復雜度O(n*m)。
1 #include <iostream> 2 #include <cstdio> 3 #include <map> 4 #include <set> 5 #include <cstring> 6 7 using namespace std; 8 9 const int kMaxN = 1000 + 5; 10 int n, m, a1[kMaxN], a2[kMaxN]; 11 12 int main(){ 13 //freopen("in.txt","r",stdin); 14 cin >> n; 15 for (int i = 1; i <= n; i ++) 16 cin >> a1[i]; 17 cin >> m; 18 for (int i = 1; i <= m; i ++) 19 cin >> a2[i]; 20 int ans = 1e9; 21 for (int i = 0; i <= m - n; i ++) { 22 int sum = 0; 23 for (int j = 1; j <= n; j ++) 24 sum += (a1[j] - a2[i + j]) * (a1[j] - a2[i + j]); 25 ans = min(ans, sum); 26 } 27 cout << ans << endl; 28 return 0; 29 }
錦標賽
題目描述
比賽有 n 個人參加(其中 n 為2的冪),每個參賽者根據資格賽和預賽、復賽的成績,會有不同的積分。比賽采取錦標賽賽制,分輪次進行,設某一輪有 m 個人參加,那么參賽者會被分為 m/2 組,每組恰好 2 人,m/2 組的人分別廝殺。我們假定積分高的人肯定獲勝,若積分一樣,則隨機產生獲勝者。獲勝者獲得參加下一輪的資格,輸的人被淘汰。重復這個過程,直至決出冠軍。
現在請問,參賽者小美最多可以活到第幾輪(初始為第0輪)?
輸入描述:
第一行一個整數 n (1≤n≤ 2^20),表示參加比賽的總人數。 接下來 n 個數字(數字范圍:-1000000…1000000),表示每個參賽者的積分。 小美是第一個參賽者。
輸出描述:
小美最多參賽的輪次。
輸入例子:
4 4 1 2 3
輸出例子:
2
題解:
這個也比較好容易想到,很容易可以想到當小美被淘汰時,所有其他人的分數都是嚴格大於小美的分數的。所以直接將小美安排和所有與她分數低的人比賽即可,因為分數高的一定贏,因此可以認為她淘汰的人和被他淘汰的人淘汰的人,這些人分數都小於等於小美。
因此答案就是log2(count(score <= score[0])) score[0]是小美的分數。
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 5 using namespace std; 6 7 int main(){ 8 //freopen("1.in","r",stdin); 9 int n,ans = 0,x0, x; 10 cin >> n; 11 for(int i = 0; i < n; i++){ 12 scanf("%d",&x); 13 if(i == 0) x0 = x; 14 ans += x <= x0; 15 } 16 cout << ((int)(log(ans * 1.0) / log(2.0))) << endl; 17 return 0; 18 }
優惠券
美團點評上有很多餐館優惠券,用戶可以在美團點評App上購買。每種優惠券有一個唯一的正整數編號。每個人可以擁有多張優惠券,但每種優惠券只能同時擁有至多一張。每種優惠券可以在使用之后繼續購買。
當用戶在相應餐館就餐時,可以在餐館使用優惠券進行消費。某人優惠券的購買和使用按照時間順序逐行記錄在一個日志文件中,運營人員會定期抽查日志文件看業務是否正確。業務正確的定義為:一個優惠券必須先被購買,然后才能被使用。
某次抽查時,發現有硬盤故障,歷史日志中有部分行損壞,這些行的存在是已知的,但是行的內容讀不出來。假設損壞的行可以是任意的優惠券的購買或者使用。
現在給一個日志文件,問這個日志文件是否正確。若有錯,輸出最早出現錯誤的那一行,即求出最大s,使得記錄1到s-1滿足要求;若沒有錯誤,輸出-1。
輸入描述:
輸入包含多組數據 m 分別表示 m (0 ≤ m ≤ 5 * 10^5) 條記錄。 下面有m行,格式為: I x (I為Input的縮寫,表示購買優惠券x); O x(O為Output的縮寫,表示使用優惠券x); ? (表示這條記錄不知道)。 這里x為正整數,且x ≤ 10^5 。
輸出描述:
-1 或 x(1 ≤ x ≤ m) 其中x為使得1到x-1這些記錄合法的最大行號。
輸入例子:
0 1 O 1 2 ? O 1 3 I 1 ? O 1 2 I 2 O 1
輸出例子:
-1 1 -1 -1 2
題解:
剛開始看到這題,就理所應當的認為只需要記錄未知記錄的個數,和每個優惠券的狀態即可,如果在購買時發現還存在這種優惠券,就用未知記錄代替,位置記錄條數減一(消費同理)。
這樣做的確是把不合理的購買或者消費記錄給清除了,但是這樣做法的一個最大問題就在於,它沒有考慮購買的時機,即認為未知記錄是萬能的。但是例如連續兩次買進同一種優惠券(中間沒有未知記錄),這樣也是不合法的。因此,我們需要分情況討論:
購買優惠券x時:
1.若之前沒有交易過優惠券x或之前最后一次操作x已經將其賣出: 直接購買
2.前一次操作是買入優惠券x:
1)兩次買進之間沒有未知記錄: 這條記錄是不合法的
2)兩次買進之間有多次(>=1)條未知記錄: 取最早的一條未知記錄充當x的使用記錄。
使用優惠券x時:
1.之前對x的操作最后一次是買入優惠券x: 直接使用
2.前一次對x的操作是對x的使用(或沒有對x的操作記錄):
1)找出這兩次使用操作的最早的一條未知記錄,充當買入操作
2)若沒有未知記錄,這條記錄不合法
上面為什么一定要取多天未知記錄的最早的一條,讀者自己可以思考下。下面我的代碼用yhq變量保存最近的上一次操作優惠券x的位置,使用set集合來存放所有未知記錄出現的位置(可以很方便的使用lower_bound函數)。
1 #include <iostream> 2 #include <cstdio> 3 #include <map> 4 #include <set> 5 #include <cstring> 6 7 using namespace std; 8 9 char c; 10 int n, x; 11 map<int, int> yhq; 12 set<int> unknown; 13 14 int main(){ 15 //freopen("in.txt","r",stdin); 16 while(~scanf("%d%*c", &n)) { 17 yhq.clear(); 18 unknown.clear(); 19 int has_error = -1; 20 for (int i = 1; i <= n; i ++) { 21 scanf("%c%*c", &c); 22 if(c == '?') { 23 unknown.insert(i); 24 continue; 25 } 26 scanf("%d%*c", &x); 27 if(has_error >= 0) { // has find ans 28 continue; 29 } 30 if(c == 'I') { 31 if(yhq[x] > 0) { 32 set<int>::iterator it = unknown.lower_bound(yhq[x]); 33 if(it == unknown.end()) has_error = i; 34 else unknown.erase(it); 35 } 36 yhq[x] = i; 37 } 38 else { 39 if(yhq[x] <= 0) { 40 set<int>::iterator it = unknown.lower_bound(-yhq[x]); 41 if(it == unknown.end()) has_error = i; 42 else unknown.erase(it); 43 } 44 yhq[x] = -i; 45 } 46 } 47 printf("%d\n", has_error); 48 } 49 return 0; 50 }
送外賣
題目描述
給定兩個整數數列 a[0]~a[n-1] 和 b[0]~b[n-1] ,在每個小區 i 里你有兩種選擇:
1) 選擇a:向前 a[i] 個小區。
2) 選擇b:向前 b[i] 個小區。
把每步的選擇寫成一個關於字符 ‘a’ 和 ‘b’ 的字符串。求到達小區n-1的方案中,字典序最小的字符串。如果做出某個選擇時,你跳出了這n個小區的范圍,則這個選擇不合法。
• 當沒有合法的選擇序列時,輸出 “No solution!”。
• 當字典序最小的字符串無限長時,輸出 “Infinity!”。
• 否則,輸出這個選擇字符串。
字典序定義如下:串s和串t,如果串 s 字典序比串 t 小,則
• 存在整數 i ≥ -1,使得∀j,0 ≤ j ≤ i,滿足s[j] = t[j] 且 s[i+1] < t[i+1]。
• 其中,空字符 < ‘a’ < ‘b’。
輸入描述:
輸入有 3 行。 第一行輸入一個整數 n (1 ≤ n ≤ 10^5)。 第二行輸入 n 個整數,分別表示 a[i] 。 第三行輸入 n 個整數,分別表示 b[i] 。 −n ≤ a[i], b[i] ≤ n
輸出描述:
輸出一行字符串表示答案。
輸入例子:
7 5 -3 6 5 -5 -1 6 -6 1 4 -2 0 -2 0
輸出例子:
abbbb
題解:
終於通過所有數據了。
1 #include <iostream> 2 #include <cstdio> 3 #include <map> 4 #include <set> 5 #include <cstring> 6 7 using namespace std; 8 9 const int kMaxN = 1e5 + 5; 10 int n, a[kMaxN], b[kMaxN]; 11 int vis[kMaxN], ans_flag; 12 char str_ans[kMaxN]; 13 14 int dfs(char * ans, int step, int cur_node, bool flag) 15 { 16 if(cur_node < 1 || cur_node > n || vis[cur_node] > 1) 17 return 0; 18 if(vis[cur_node]) return 1; 19 if(cur_node == n) { 20 ans[step] = 0; 21 ans_flag = flag; 22 if(!flag) puts(ans); 23 return 2; 24 } 25 vis[cur_node] = 1; 26 ans[step] = 'a'; 27 int ret = dfs(ans, step + 1, cur_node + a[cur_node], flag); 28 if(ret == 2) return 2; 29 30 ans[step] = 'b'; 31 if(dfs(ans, step + 1, cur_node + b[cur_node], flag || ret==1) == 2) 32 return 2; 33 34 vis[cur_node] = 2; 35 return 0; 36 } 37 38 int main(){ 39 //freopen("in.txt","r",stdin); 40 cin >> n; 41 for (int i = 1; i <= n; i ++) 42 scanf("%d", &a[i]); 43 for (int i = 1; i <= n; i ++) 44 scanf("%d", &b[i]); 45 memset(vis, 0, sizeof(vis)); 46 if(dfs(str_ans, 0, 1, 0) != 2) 47 puts("No solution!"); 48 else if(ans_flag){ 49 puts("Infinity!"); 50 } 51 return 0; 52 }
數碼
空間限制:32768K
題目描述
輸入描述:
一行,兩個整數 l 和 r (1 ≤ l ≤ r ≤ 10^9)。
輸出描述:
輸出9行。
第 i 行,輸出數碼 i 出現的次數。
輸入例子:
1 4
輸出例子:
4 2 1 1 0 0 0 0 0
題解:
首先,使用find_ans(n)計算1~n所有數的答案,那么最后的答案就是find_ans(r) - find_ans(l - 1)。
其次,要計算最高位為i的約數出現的次數。例如i=2,那么另cnt(x)為n以內約數包含x的數的個數,有:
ans[2] = cnt(2) + cnt(20) + cnt(21) + .... cnt(29) + cnt(200) + cnt(201) + ... 其中(x<=n)
所以我們分位數討論
1位[i, i+1),2位[i*10, (i+1)*10), 3位[i*100, (i+1)*100)... ... 其中i取1~9。
我們知道,n以內包含約數x的自然數的個數為 n / x,即cnt(x) = n / x, 我們令ans[i]表示首位為i的答案,那么有:
ans[i] = sum{ sum{ n / x | i * 10^j <= x <= (i + 1) * 10^j} | 0 <= j <= 9}
上述步驟中可以看到對於每一個j, 即對於最高位為i且位數為(j+1)位的所有數x是連續的。 所以關鍵在於寫一個函數,用於計算:
sigle_sum = (n / low) + (n / (low + 1)) + ... + (n / high) 其中 low = i * 10^j, high = (i + 1) * 10^j
可以看到上述計算單個答案sigle_sum 時,復雜度為O(10^j), 這對於一個位數較大(如j>=7)情況來說,枚舉量依然非常巨大。
但是我們可以看到當j>=5時,low = i * 10^5, 有 n/low <= 10^4,所以我們可以枚舉上述每一個式子的商(如商=x),再計算[low,high]區間中商為x的數有多少個,最后答案+ x * num(low, high, n, x),其中num(low, high, n, x)表示n/low, n/(low+1),,,n/(high)這些值中結果為x的個數,這個我們可以O(1)的計算出來。
最后計算sigle_sum總復雜度小於O(1e5)。
最后計算總答案復雜度:O(9 * 10 * 1e5),終於可以通過了~
1 #include <iostream> 2 #include <cstdio> 3 #include <map> 4 #include <set> 5 #include <cstring> 6 7 using namespace std; 8 9 typedef long long LL; 10 LL l, r; 11 LL a[10], b[10]; 12 13 // calc (n / low) + (n / (low + 1)) + ... + (n / high) 14 LL find_ans(LL low, LL high, LL n) 15 { 16 if(high < low) return 0; 17 LL result = 0; 18 if(high - low < 1e5) for (int i = low; i <= high; i ++) { 19 result += n / i; 20 } 21 else { 22 LL l = n / high, r = n / low; 23 for (int i = l; i <= r; i ++) { 24 LL lb = max(n / (i + 1), low), rb = min(n / i, high); 25 if(n / rb == n / lb) rb ++; 26 result += (rb - lb) * i; 27 } 28 } 29 return result; 30 } 31 32 void find_ans(LL n, LL * ans) 33 { 34 memset(ans, 0, sizeof(LL) * 10); 35 if(!n) return ; 36 for (int i = 1; i <= 9; i ++) { 37 LL mul_num = 1; 38 for (int j = 0; j <= 9 && mul_num <= n; j ++) { 39 ans[i] += find_ans(i * mul_num, min((i + 1) * mul_num - 1, n), n); 40 mul_num *= 10; 41 } 42 } 43 } 44 45 int main(){ 46 //freopen("in.txt","r",stdin); 47 cin >> l >> r; 48 find_ans(l - 1, a); 49 find_ans(r, b); 50 for (int i = 1; i <= 9; i ++) 51 cout << (b[i] - a[i]) << endl; 52 return 0; 53 }
圍棋
題目描述
1. 棋盤19*19。
2. 棋子分黑白兩色,雙方各執一色。
3. 下法:每次黑或白着一子於棋盤的空點上。棋子下定后,不再向其他點移動。
4. 棋子的氣:一個棋子在棋盤上,與它相鄰的空點是這個棋子的“氣”(這里相鄰是指兩個點有公共邊)。 相鄰的點上如果有同色棋子存在,這些棋子就相互連接成一個不可分割的整體,氣合並計算。
相鄰的點上如果有異色棋子存在,此處的氣便不存在。
如果棋子所在的連通塊失去所有的氣,即為無氣之子,不能在棋盤上存在。
5. 提子:把無氣之子清理出棋盤的手段叫“提子”。提子有二種:
1) 着子后,對方棋子無氣,應立即提取對方無氣之子。
2) 着子后,雙方棋子都呈無氣狀態,應立即提取對方無氣之子。
6. 禁着點:棋盤上的任何一空點,如果某方在此下子,會使該子立即呈無氣狀態,同時又不能提取對方的棋子,這個點叫做“禁着點”,該方不能在此下子。
7. 禁止全局同形:無論哪一方,在成功進行了着子、提子操作后,棋盤局面不能和任何之前的局面相同。
你要做的是:輸入一些操作,從空棋盤開始模擬這些操作。
對於每一步,若結果不正確,則輸出對應的miss並且忽略這個操作,並在最后輸出棋盤的局面。
輸入描述:
第一行,測試數據組數≤100 第二行,每組測試數據,執行的步數 n ≤ 2000 然后 n 行 B x y W x y (1 ≤ x ≤ 19,1 ≤ y ≤ 19) 其中,二元組 x,y 表示圍棋棋盤上第 x 行第 y 列對應的點。 輸入數據保證是黑白輪流下的。
輸出描述:
多行 對於miss的情況,輸出是哪一種錯誤格式,其中: miss 1 表示下的位置已經有棋了 miss 2 表示違反規則6 miss 3 表示違反規則7 對於正常的操作,不用輸出。 最后輸出最終盤面。“B表示黑子,W表示白子,如果是空點的話,就輸出'.'字符。”
輸入例子:
1 12 B 1 3 W 1 2 B 2 4 W 2 1 B 1 1 W 2 3 B 3 3 W 3 2 B 1 1 W 2 3 B 2 2 W 2 3 對應的棋形是這樣的:

輸出例子:
miss 2 miss 2 miss 1 miss 3 .WB................ WB.B............... .WB................ ................... ................... ................... ................... ................... ................... ................... ................... ................... ................... ................... ................... ................... ................... ................... ...................
題解:
這個比較純粹,模擬每一次操作,然后判斷是否合法,沒太多可講之處,純粹是考察代碼實現能力。當然我也只是完成了這道題而已啦,代碼風格和可讀性還比較低。
1 #include <iostream> 2 #include <cstdio> 3 #include <map> 4 #include <set> 5 #include <cstring> 6 #include <vector> 7 #include <queue> 8 9 using namespace std; 10 11 const int kModNum = 1e9 + 7; 12 const int kBorderSize = 19; 13 const int dir[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; 14 15 struct Board { 16 int arr[20][20]; 17 int hash_val; 18 bool operator == (const Board & another) const { 19 for (int i = 0; i < kBorderSize; i ++) { 20 for (int j = 0; j < kBorderSize; j ++) { 21 if(arr[i][j] != another.arr[i][j]) return false; 22 } 23 } 24 return true; 25 } 26 bool operator < (const Board & another) const { 27 return hash_val < another.hash_val; 28 } 29 }; 30 31 int T, n, x, y; 32 char op; 33 Board board, tmp; 34 map< int, set<Board> > mps; 35 int vis[kBorderSize][kBorderSize]; 36 37 38 void Reset() 39 { 40 memset(board.arr, 0, sizeof(board.arr)); 41 mps.clear(); 42 } 43 44 int MyHash(const Board & board) 45 { 46 int hash_num = 0; 47 for (int i = 0; i < kBorderSize; i ++) { 48 for (int j = 0; j < kBorderSize; j ++) { 49 hash_num = ((hash_num << 1) % kModNum + hash_num) % kModNum; 50 hash_num = (hash_num + board.arr[i][j]) % kModNum; 51 } 52 } 53 return hash_num; 54 } 55 56 bool OutBound(int nx, int ny) 57 { 58 return nx < 0 || nx >= kBorderSize || ny < 0 || ny >= kBorderSize; 59 } 60 61 bool ExistBoard(const Board board) 62 { 63 return mps.count(board.hash_val) && mps[board.hash_val].count(board); 64 } 65 66 bool IsDeadPiece(int x, int y, int piece_type) 67 { 68 memset(vis, false, sizeof(vis)); 69 queue<int> que; 70 que.push(x * kBorderSize + y); 71 vis[x][y] = true; 72 while(!que.empty()) { 73 int fr = que.front(); 74 que.pop(); 75 x = fr / kBorderSize; 76 y = fr % kBorderSize; 77 for (int i = 0; i < 4; i ++) { 78 int nx = x + dir[i][0], ny = y + dir[i][1]; 79 if(OutBound(nx, ny) || vis[nx][ny]) continue; 80 if(!board.arr[nx][ny]) { 81 return false; 82 } 83 if(board.arr[nx][ny] == piece_type) { 84 que.push(nx * kBorderSize + ny); 85 vis[nx][ny] = true; 86 } 87 } 88 } 89 return true; 90 } 91 92 void RemoveDeadPiece() 93 { 94 for (int i = 0; i < kBorderSize; i ++) { 95 for (int j = 0; j < kBorderSize; j ++) { 96 if(vis[i][j]) board.arr[i][j] = 0; 97 } 98 } 99 } 100 101 bool MovePiece() 102 { 103 int put_val = op == 'B' ? 1 : 2; 104 // 放下這顆棋子 105 board.arr[x][y] = put_val; 106 // 找是否能移除對手的棋子 107 bool can_remove = false; 108 for (int i = 0; i < 4; i ++) { 109 int nx = x + dir[i][0], ny = y + dir[i][1]; 110 if(OutBound(nx, ny) || board.arr[nx][ny] != 3 - put_val) 111 continue; 112 if(IsDeadPiece(nx, ny, board.arr[nx][ny])) { 113 can_remove = true; 114 } 115 } 116 // 不能移走對手的棋子,自己又是死棋,不能下在這 117 if(!can_remove && IsDeadPiece(x, y, put_val)) 118 return false; 119 // 吧對手的死棋移除 120 for (int i = 0; i < 4; i ++) { 121 int nx = x + dir[i][0], ny = y + dir[i][1]; 122 if(OutBound(nx, ny) || board.arr[nx][ny] != 3 - put_val) 123 continue; 124 if(IsDeadPiece(nx, ny, board.arr[nx][ny])) { 125 RemoveDeadPiece(); 126 } 127 } 128 board.hash_val = MyHash(board); 129 return true; 130 } 131 132 void PrintBoard(Board board) 133 { 134 for (int i = 0; i < kBorderSize; i ++) { 135 for (int j = 0; j < kBorderSize; j ++) { 136 if(!board.arr[i][j]) putchar('.'); 137 else if(board.arr[i][j] & 1) putchar('B'); 138 else putchar('W'); 139 } 140 puts(""); 141 } 142 } 143 144 int main(){ 145 //freopen("in.txt","r",stdin); 146 cin >> T; 147 while(T--) { 148 Reset(); 149 scanf("%d%*c", &n); 150 while(n --) { 151 scanf("%c %d %d%*c", &op, &x, &y); 152 x --; y --; 153 // 第一種錯誤, 位置已經存在棋子 154 if(board.arr[x][y]) { 155 puts("miss 1"); 156 continue; 157 } 158 tmp = board; 159 if(!MovePiece()) { 160 puts("miss 2"); 161 board = tmp; 162 continue; 163 } 164 if(ExistBoard(board)) { 165 puts("miss 3"); 166 board = tmp; 167 continue; 168 } 169 mps[MyHash(board)].insert(board); 170 } 171 PrintBoard(board); 172 } 173 return 0; 174 }