2019藍橋杯國賽備賽題庫


代碼填空題

全排列   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;
}
View Code

這個函數可以求出去重后的全排列。如果不填入代碼,輸入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;
}
View Code

快速冪求余算法,鏈接,答案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;
}
View Code

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;
}
View Code

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;
}
View Code

先把函數的每個參數的含義搞清楚,第一個參數是不同環數的分數,第二個參數記錄每一環中的環數,第三個參數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;
}
View Code

如果把 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點的序號。

1300692-20180503161631084-323353556

#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;
}
View Code

先讀懂題意,就拿第一個圖和第二個圖來講,第一個圖經過了①關於x軸的對稱變換(-x, y) 在經過②順時針旋轉90度變換(y,x)

因此第一個if語句中填入的是f(n-1,y,x),n-1是遞歸降階,懂了第一個,剩下的三個if語句也就很好理解了。答案:m-y-1

image


瓷磚樣式  By 藍橋杯第八屆B組決賽

小明家的一面裝飾牆原來是 3*10 的小方格。 現在手頭有一批剛好能蓋住2個小方格的長方形瓷磚。

瓷磚只有兩種顏色:黃色和橙色。

小明想知道,對於這么簡陋的原料,可以貼出多少種不同的花樣來。 小明有個小小的強迫症:忍受不了任何2*2的小格子是同一種顏色。
  (瓷磚不能切割,不能重疊,也不能只鋪一部分。另外,只考慮組合圖案,請忽略瓷磚的拼縫)

顯然,對於 2*3 個小格子來說,口算都可以知道:一共10種貼法,如圖所示
但對於 3*10 的格子呢?肯定是個不小的數目,請你利用計算機的威力算出該數字。

2

利用位來去重,一共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;
}
View Code


免責聲明!

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



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