代碼填空題
全排列 By 2018計蒜客藍橋杯省賽B組模擬
相信大家都知道什么是全排列,但是今天的全排列比你想象中的難一點。我們要找的是全排列中,排列結果互不相同的個數。比如:aab
的全排列就只有三種,那就是aab
,baa
,aba
。
代碼框中的代碼是一種實現,請分析並填寫缺失的代碼。

#include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N=1e3; char str[N], buf[N];//buffer int vis[N], total, len; void arrange(int num) { if (num == len){ printf("%s\n", buf); total++; return; } for (int i = 0; i < len; ++i) { if (!vis[i]) { int j; for (j = i + 1; j < len; ++j) { if (/*填入代碼*/) { break; } } if (j == len) { vis[i] = 1; buf[num] = str[i]; arrange(num + 1); vis[i] = 0; } } } } int main() { while (~scanf("%s",str)) { len = strlen(str); sort(str, str + len); total = 0; buf[len] = '\0'; arrange(0); printf("Total %d\n", total); } return 0; }
這個函數可以求出去重后的全排列。如果不填入代碼,輸入aab,則輸出aab aab aba aba baa baa。與題意不符,因此推斷,填入代碼所在的for循環是用來去重的。
第一個a作為第一個元素形成的序列有aab aba ,如果第二個a開頭,那么形成的序列必然和先前的重復。
這種情況可以翻譯成:str[j] == s[i] && vis[j] == 1 (存在 j > i),故填入代碼str[i] == str[j] && vis[j]
快速冪 By 2018計蒜客藍橋杯省賽B組模擬
一個數的整數次冪,是我們在計算中經常用到的,但是怎么可以在 O(log(n)) 的時間內算出結果呢?
代碼框中的代碼是一種實現,請分析並填寫缺失的代碼,求 x^ymod p 的結果。

#include <iostream> using namespace std; int pw(int x, int y, int p) { if (!y) { return 1; } int res = ——————————————; if (y & 1) { res = res * x % p; } return res; } int main() { int x, y, p; cin >> x >> y >> p; cout << pw(x, y, p) << endl; return 0; }
快速冪求余算法,鏈接,答案pw(x*x, y<<1, p)
Lis By 2018計蒜客藍橋杯省賽B組模擬
LIS 是最長上升子序列。什么是最長上升子序列? 就是給你一個序列,請你在其中求出一段最長嚴格上升的部分,它不一定要連續。
就像這樣:2, 3, 4, 7 和 2, 3, 4, 6 就是序列 2 5 3 4 1 7 6 的兩個上升子序列,最長的長度是 4。

#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 9; int f[N], a[N]; int n; int find(int l, int r, int x) { while (l < r) { int mid = (l + r) / 2; if (f[mid] < x) { l = mid + 1; } else { r = mid; } } return l; } int lis() { int len = 0; for (int i = 0; i < n; i++) { ________ f[k] = a[i]; if (k == len) { len++; } } return len; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", a + i); } printf("%d\n", lis()); return 0; }
O(logn)的LIS算法,答案 int k = find(0, len, a[i]);
信號匹配 By 藍橋杯第五屆B組決賽
從X星球接收了一個數字信號序列。
現有一個已知的樣板序列。需要在信號序列中查找它首次出現的位置。這類似於串的匹配操作。
如果信號序列較長,樣板序列中重復數字較多,就應當注意比較的策略了。可以仿照串的KMP算法,進行無回溯的匹配。這種匹配方法的關鍵是構造next數組。
next[i] 表示第i項比較失配時,樣板序列向右滑動,需要重新比較的項的序號。如果為-1,表示母序列可以進入失配位置的下一個位置進行新的比較。
下面的代碼實現了這個功能,請仔細閱讀源碼,推斷划線位置缺失的代碼。

// 生成next數組 int* make_next(int pa[], int pn) { int* next = (int*)malloc(sizeof(int)*pn); next[0] = -1; int j = 0; int k = -1; while(j < pn-1){ if(k==-1 || pa[j]==pa[k]){ j++; k++; next[j] = k; } else k = next[k]; } return next; } // da中搜索pa, da的長度為an, pa的長度為pn int find(int da[], int an, int pa[], int pn) { int rst = -1; int* next = make_next(pa, pn); int i=0; // da中的指針 int j=0; // pa中的指針 int n = 0; while(i<an){ n++; if(da[i]==pa[j] || j==-1){ i++; j++; } else __________________________; //填空位置 if(j==pn) { rst = i-pn; break; } } free(next); return rst; } int main() { int da[] = {1,2,1,2,1,1,2,1,2,1,1,2,1,1,2,1,1,2,1,2,1,1,2,1,1,2,1,1,1,2,1,2,3}; int pa[] = {1,2,1,1,2,1,1,1,2}; int n = find(da, sizeof(da)/sizeof(int), pa, sizeof(pa)/sizeof(int)); printf("%d\n", n); return 0; }
KMP算法,答案 j = next[j];
打靶 By 藍橋杯第七屆A組決賽
小明參加X星球的打靶比賽。
比賽使用電子感應計分系統。其中有一局,小明得了96分。這局小明共打了6發子彈,沒有脫靶。但望遠鏡看過去,只有3個彈孔。顯然,有些子彈准確地穿過了前邊的彈孔。
不同環數得分是這樣設置的:
1,2,3,5,10,20,25,50
那么小明的6發子彈得分都是多少呢?有哪些可能情況呢?下面的程序解決了這個問題。

#include <stdio.h> #define N 8 void f(int ta[], int da[], int k, int ho, int bu, int sc) { int i,j; if(ho<0 || bu<0 || sc<0) return; if(k == N){ if(ho>0 || bu>0 || sc>0) return; for(i=0; i<N; i++){ for(j=0; j<da[i]; j++) printf("%d ", ta[i]); } printf("\n"); return; } for(i=0; i<=bu; i++){ da[k] = i; f(ta, da, k+1,____, bu-i, sc-ta[k]*i); //填空位置 } da[k] = 0; } int main() { int ta[] = {1,2,3,5,10,20,25,50}; int da[N]; f(ta, da, 0, 3, 6, 96); return 0; }
先把函數的每個參數的含義搞清楚,第一個參數是不同環數的分數,第二個參數記錄每一環中的環數,第三個參數k記錄當前遞歸的層數(滿為N),第四個參數是彈孔數量,第五個是射擊次數,第六個是成績。ok,此題就基本可以做出來了。
答案:ho - i==0? 0 : 1
棋子換位 By 藍橋杯第七屆B組決賽
有n個棋子A,n個棋子B,在棋盤上排成一行。它們中間隔着一個空位,用“.”表示,比如:AAA.BBB
現在需要所有的A棋子和B棋子交換位置。
移動棋子的規則是:
1. A棋子只能往右邊移動,B棋子只能往左邊移動。
2. 每個棋子可以移動到相鄰的空位。
3. 每個棋子可以跳過相異的一個棋子落入空位(A跳過B或者B跳過A)。
AAA.BBB 可以走法:
移動A ==> AA.ABBB
移動B ==> AAAB.BB
跳走的例子:
AA.ABBB ==> AABA.BB

#include <stdio.h> #include <string.h> void move(char* data, int from, int to) { data[to] = data[from]; data[from] = '.'; } int valid(char* data, int k) { if(k<0 || k>=strlen(data)) return 0; return 1; } void f(char* data) { int i; int tag; int dd = 0; // 移動方向 while(1){ tag = 0; for(i=0; i<strlen(data); i++){ if(data[i]=='.') continue; if(data[i]=='A') dd = 1; if(data[i]=='B') dd = -1; if(valid(data, i+dd) && valid(data,i+dd+dd) && data[i+dd]!=data[i] && data[i+dd+dd]=='.'){ //如果能跳... move(data, i, i+dd+dd); printf("%s\n", data); tag = 1; break; } } if(tag) continue; for(i=0; i<strlen(data); i++){ if(data[i]=='.') continue; if(data[i]=='A') dd = 1; if(data[i]=='B') dd = -1; if(valid(data, i+dd) && data[i+dd]=='.'){ // 如果能移動... if( ______________________ ) continue; //填空位置 move(data, i, i+dd); printf("%s\n", data); tag = 1; break; } } if(tag==0) break; } } int main() { char data[] = "AAA.BBB"; f(data); return 0; }
如果把 if 那條語句注釋掉,那么打印出的結果是
1移動 AA.ABBB
2跳走 AABA.BB
3移動 AAB.ABB
4跳走 A.BAABB
5移動 .ABAABB
6跳走 BA.AABB
7移動 B.AAABB
f函數中,第一個for循環是實現跳走的,第二個for循環是實現移動的,如果注釋掉那條if語句,程序的“移動”功能是錯誤的。我們簡單模擬程序運行的過程,第一個移動是不會錯的,我們假設第二個移動錯誤,那么把AABA.BB這種情況抽象出來,那就是:
當data[i-dd] == data[i + dd +dd]的時候(i指向第三個A),不能移動
結合第一個for循環中,if條件語句的寫法,我們還需要加上
valid(data, i –dd) && valid(data, i – dd –dd)
最終需要填入的代碼為:data[i-dd] == data[i + dd +dd] && valid(data, i –dd) && valid(data, i – dd –dd) ,結果正確,假設成立。
代碼填空,我們如果想主要通過模擬來推測代碼,往往是比較困難的,因為我們模擬的想法和程序的作者的想法往往有差異,因此,可以通過注釋其所在語句,來推測代碼的功能,然后假設幾種情況分別帶入驗證。
希爾伯特曲線 By 藍橋杯第八屆B組決賽
希爾伯特曲線是以下一系列分形曲線 Hn 的極限。我們可以把 Hn 看作一條覆蓋 2^n × 2^n 方格矩陣的曲線,曲線上一共有 2^n *2^n 個頂點(包括左下角起點和右下角終點),恰好覆蓋每個方格一次。 Hn(n > 1)可以通過如下方法構造:
1. 將 Hn-1 順時針旋轉90度放在左下角
2. 將 Hn-1 逆時針旋轉90度放在右下角
3. 將2個 Hn-1 分別放在左上角和右上角
4. 用3條單位線段把4部分連接起來
對於 Hn 上每一個頂點 p ,
我們定義 p 的坐標是它覆蓋的小方格在矩陣中的坐標(左下角是(1, 1),右上角是(2^n, 2^n),從左到右是X軸正方向,從下到上是Y軸正方向), 定義 p 的序號是它在曲線上從起點開始數第幾個頂點(從1開始計數)。 以下程序對於給定的n(n <= 30)和p點坐標(x, y),輸出p點的序號。

#include <stdio.h> long long f(int n, int x, int y) { if (n == 0) return 1; int m = 1 << (n - 1); if (x <= m && y <= m) { return f(n - 1, y, x); } if (x > m && y <= m) { return 3LL * m * m + f(n - 1,__, 2 * m - x + 1); //填空 } if (x <= m && y > m) { return 1LL * m * m + f(n - 1, x, y - m); } if (x > m && y > m) { return 2LL * m * m + f(n - 1, x - m, y - m); } } int main() { int n, x, y; scanf("%d %d %d", &n, &x, &y); printf("%lld", f(n, x, y)); return 0; }
先讀懂題意,就拿第一個圖和第二個圖來講,第一個圖經過了①關於x軸的對稱變換(-x, y) 在經過②順時針旋轉90度變換(y,x)
因此第一個if語句中填入的是f(n-1,y,x),n-1是遞歸降階,懂了第一個,剩下的三個if語句也就很好理解了。答案:m-y-1
瓷磚樣式 By 藍橋杯第八屆B組決賽
小明家的一面裝飾牆原來是 3*10 的小方格。 現在手頭有一批剛好能蓋住2個小方格的長方形瓷磚。
瓷磚只有兩種顏色:黃色和橙色。
小明想知道,對於這么簡陋的原料,可以貼出多少種不同的花樣來。 小明有個小小的強迫症:忍受不了任何2*2的小格子是同一種顏色。
(瓷磚不能切割,不能重疊,也不能只鋪一部分。另外,只考慮組合圖案,請忽略瓷磚的拼縫)
顯然,對於 2*3 個小格子來說,口算都可以知道:一共10種貼法,如圖所示
但對於 3*10 的格子呢?肯定是個不小的數目,請你利用計算機的威力算出該數字。
利用位來去重,一共30個瓷磚,也就是30位,int是32位,因此int足夠用。

#include <stdio.h> #include <string.h> #include <map> #include <algorithm> #include <iostream> using namespace std; const int w = 3, h = 10; int graph[w][h]; int ans = 0; map<int, int> Hash; //檢查2x2格子中顏色是否相同 bool check_color() { for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { if (i + 1 < w && j + 1 < h) if ((graph[i][j] + graph[i][j + 1] + graph[i + 1][j] + graph[i + 1][j + 1]) % 4 == 0) return false; } } return true; } //檢測方案是否有重復 bool check_repeat() { int ret = 0, bit = 1; for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { ret += graph[i][j] * bit; bit <<= 1; } } //此塗色方案未曾出現 if (Hash.count(ret) == 0) { Hash[ret] = 1; return true; } else return false; } void fill_with_tile(int n) { //已經鋪滿 if (n == w * h) { if (check_color() == true && check_repeat() == true) ans++; return; } int x = n / h; int y = n % h; //此塊未被塗色 if (graph[x][y] == -1) { //橫向擺放 if (y + 1 < h && graph[x][y + 1] == -1) { //枚舉兩種顏色 for (int i = 0; i < 2; i++) { graph[x][y] = graph[x][y + 1] = i; fill_with_tile(n + 1); //回溯 graph[x][y] = graph[x][y + 1] = -1; } } //縱向擺放 if (x + 1 < w && graph[x + 1][y] == -1) { //枚舉兩種顏色 for (int i = 0; i < 2; i++) { graph[x][y] = graph[x + 1][y] = i; fill_with_tile(n + 1); //回溯 graph[x][y] = graph[x + 1][y] = -1; } } } else fill_with_tile(n + 1); } int main() { memset(graph, -1, sizeof(graph)); fill_with_tile(0); printf("%d\n", ans); return 0; }