1. 程式人生 > 實用技巧 >2016年第七屆藍橋杯【C++省賽B組】F、G、H、J 題解

2016年第七屆藍橋杯【C++省賽B組】F、G、H、J 題解

F. 方格填數 #深搜

題意

\(10\)個格子,填入0~9的數字。要求:連續的兩個數字不能相鄰。(左右、上下、對角都算相鄰),求可能的填數方案數。

   +--+--+--+
   |  |  |  |
+--+--+--+--+
|  |  |  |  |
+--+--+--+--+
|  |  |  |  
+--+--+--+

分析

題目有點表述不清,實際上要我們在所有格子中只填一種數字。題目中的“相鄰”,是指元素在位置上的相鄰。而“連續”,是指數字意義上的連續,比如4 5 7,中間的5,它與右邊的7不是連續的,但它與左邊的4連續,即\(5 - 4 = 1\)。因此,在判斷是否連續的時候,需要判斷特定點的左、左上、上、右上的數字,與特定點的絕對值之差是否為\(1\)

,若為\(1\)則說明相鄰數字是連續的,不合題意。

由於資料規模不大,我們從第一行第二列開始搜,第三行第三列作為終點。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <string>
#include <cstring>
#include <vector>
#include <cmath>
#include <unordered_map>
#include <set>
#include <cmath>
using namespace std;
using ll = long long;
const int MAXN = 105;
int Ma[5][5], ans = 0, used[11];
int dx[] = {0, -1, -1, -1};
int dy[] = {-1, -1, 0, 1}; //左、左上、上、右上
bool Judge(int x, int y, int num){ //四個方向判斷該點是否滿足題意要求
    for(int t = 0; t < 4; t++){
        int nx = x + dx[t], ny = y + dy[t];
        if(1 <= nx && nx <= 3 && 1 <= ny && ny <= 4 && abs(Ma[nx][ny] - num) == 1)
            return false;
    }
    return true;
}
void DFS(int x, int y){
    if(x >= 3 && y >= 4){
        ans++;
        return;
    }
    for(int i = 0; i < 10; i++){ //列舉數字
        if(used[i]) continue; //之前路徑已經使用過該數字
        if(Judge(x, y, i)){
            Ma[x][y] = i;
            used[i] = true; 
            if(y + 1 <= 4) DFS(x, y + 1);
            else DFS(x+1, 1); //列數已達到邊界,到下一行的第一列
            used[i] = false;
        }
    }
}
int main(){
	for(int i = 0; i <= 4; i++)
        for(int j = 0; j <= 4; j++) Ma[i][j] = -2; //填個不會與0-9相差一的數字。
	DFS(1, 2); //從第一行第二列開始搜
	cout << ans << endl;
	//cout << 1580 << endl;
    return 0;
}

G. 剪郵票 #列舉 #連通性

題意

\(12\)張連在一起的\(12\)生肖的郵票。現在你要從中剪下\(5\)張來,要求必須是連著的。(僅僅連線一個角不算相連)現要你計算共有多少種不同的剪取方法。

分析

要注意到郵票當中每個格子的數字是不相同的!只要選定的路徑不同,就代表了不同的剪取方法了。

不妨定義個狀態表,如a[12] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1};,代表一開始的時候,第\(0\)\(6\)個格子"不使用",第\(7\)\(11\)個格子會"使用"。接下來就用全排列函式去列舉這個狀態表。

在當前的狀態表下,我們就要判斷在實際位置中"使用"的格子,是否是連通的!如何判斷連通?從某一個“使用”的格子出發,深搜去尋找每一個有標記“使用”的格子。如果“使用”的格子是相互連通的,按理說,只需要深搜一次,否則,就是不連通的。

#include <string>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <map>
#include <vector>
#include <deque>
#include <algorithm>
#include <unordered_map>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
int ans = 0, used[5][5];
int a[12] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1};
int dx[] = {1, 0, -1, 0};
int dy[] = {0, -1, 0, 1};
void DFS(int x, int y){
    for(int t = 0; t < 4; t++){
        int nx = x + dx[t], ny = y + dy[t];
        if(1 <= nx && nx <= 3 && 1 <= ny && ny <= 4 && used[nx][ny]){ 
            used[nx][ny] = 0; //消除標記
            DFS(nx, ny);
        }
    }
}
int main(){
    do{
        memset(used, 0, sizeof(used));
        int k = 0, cnt = 0;
        for(int i = 1; i <= 3; i++)
            for(int j = 1; j <= 4; j++)
                used[i][j] = a[k++];
        for(int i = 1; i <= 3; i++){
            for(int j = 1; j <= 4; j++){
                if(used[i][j]){ //找到特定點
                    cnt++;
                    DFS(i, j); //從該特定點出發,去將所有與之連通的點進行訪問,並消除它們的標記
                }
            }
        }
        if(cnt == 1) ans++; //說明只有一個連通塊
    }while(next_permutation(a, a + 12)); //列舉這12個數的全排列
    printf("%d\n", ans);
}

H. 四平方和 #暴力 #雜湊表

題意

每個正整數都可以表示為至多\(4\)正整數的平方和。如果把0包括進去,就正好可以表示為\(4\)個數的平方和。如:

$ 5 = 0^2 + 0^2 + 1^2 + 2^2 $
\(7 = 1^2 + 1^2 + 1^2 + 2^2\)

對於一個給定的正整數\(N\)(\(\leq 5000000\)),可能存在多種平方和的表示法。要求你對4個數排序\(0 \leq a \leq b \leq c \leq d\)
並對所有的可能表示法按 \(a,b,c,d\) 為聯合主鍵升序排列,最後輸出第一個表示法。

分析

該題在NOJ中三重迴圈可以過,這裡給一下\(O(\sqrt{N}\times\sqrt{N})\)的程式碼。(感謝為神