1. 程式人生 > >2015年第六屆藍橋杯省賽(C/C++ B組)解題報告

2015年第六屆藍橋杯省賽(C/C++ B組)解題報告

第1題:統計不含4的數字

題目大意

  統計10000至99999中,不包含4的數值個數。

題解

  (@『落』常笑鷹 提供)直接推匯出數學公式
  最高位除了0、4不能使用,其餘8個數字(1,2,3,5,6,7,8,9)均能使用,剩下的四位(千位、百位、十位、個位)可以使用除了4以外的所有數字,所以共有 8*9*9*9*9種解,計算得答案為:52488。

第2題:計算1千天後的日期

題目大意

  2014-11-09再過1000天是哪一日?

題解

  直接開啟Excel計算:
  使用Excel計算日期
  (記得結果要按2017-08-05的格式提交)

擴充套件些Excel知識

  Excel其實是用一個實數來儲存日期,這個實數的整數部分,就是1900年1月1日到當前日期的間隔天數,所以日期與整數間,可以直接做加減運算。
  有時候日期小數部分的值非0,小數部分就是對該天24小時的精確指定。
  Excel裡的日期實際是個實數值

  Excel是很強大的,閒著無聊的同學,也可以用下圖的方式把日期改成題目要求的格式:
  Excel自定義日期顯示格式
  
  一些元老級的參賽選手,估計還做過“高斯日記”這題,那題也是可以用Excel來解:
  使用Excel解高斯日記
  
  擴充套件的擴充套件:Excel的日期計算其實是有個很小的bug的,這在Joel的《軟體隨想錄》中有提到他在微軟的經歷,挺有趣的一段歷史遺留問題,有興趣的同學不妨看看他的書。

第3題:豎式加法

題目大意

  祥 瑞 生 輝
  三 羊 獻 瑞

 三 羊 生 瑞 氣
 
  題目用了8個不同的漢字,表示0~9裡八種不同的數字。組成兩個數值相加,等於第三個數值。

題解

  定義一個數組int a[10],初始化用a[i]儲存i。將a[2]~a[9]與各個漢字對應,然後用next_permutation全排列暴力解,注意三個數值開頭不能為0,能解出第2個數值,即“三羊獻瑞”對應的數字是1085。

 #include <cstdio>
 #include <algorithm>
using namespace std;

int main() {
    int a[10];
    for (int i = 0; i < 10; i++) a[i] = i;

    do {
        if (!a[2] || !a[6]) continue;
        int x =              a[2]*1000 + a[3]*100 + a[4]*10 + a[5];
        int y =              a[6]*1000 + a[7]*100 + a[8]*10 + a[3];
        int z = a[6]*10000 + a[7]*1000 + a[4]*100 + a[3]*10 + a[9];
        if (x + y == z) printf("%d + %d = %d\n", x, y, z);
    } while (next_permutation(a, a+10));

    return 0;
}

  當然,這題也可以用數學知識簡單的推匯出一部分數字後,再暴力解剩下的部分。

第4題:古怪的星號修飾符

題目大意

  是道程式碼填空題,主要是完成一個字串s,按寬度width截斷後,在固定寬度為width,兩邊為符號’|’的居中輸出。
  難點是題目給出了printf(“%*s%s%*s”,___),要求填printf的實參列表。

題解

  有些童鞋可能知道scanf裡用*修飾符,是起到過濾讀入的作用。比如一個有三列數值的資料,我只想得到第2列數值,可以在迴圈裡用scanf(“%*d%d%*d”, a[i])來讀入第i行的第2個數值到a[i]。
  * 修飾符在printf中的含義完全不同。如果寫成printf(“%6d”, 123),很多童鞋應該就不會陌生了,這是設定域寬的意思。同理,%6s也是域寬。* 修飾符正是用來更靈活的控制域寬。使用%*s,表示這裡的具體域寬值由後面的實參決定,如printf(“%*s”, 6, “abc”)就是把”abc”放到在域寬為6的空間中右對齊。
  明白了 * 是用變數來控制域寬,那麼這題就簡單了,這裡應該填寫5個實參。然後字元長度的計算應該用buf而不是s,因為buf才是截斷後的長度,用s的話,如果s長度超過了width-2,效果就不對了。
  簡單還原現場及參考答案:

 #include <stdio.h>
 #include <string.h>

int main() {
    char s[100] = "abcd1234";
    char buf[1000];
    int width = 20;

    strcpy(buf, s);
    buf[width-2] = 0;

    printf("|%*s%s%*s|\n", (width-strlen(buf)-2)/2, "", buf, (width-strlen(buf)-2)/2, "");
    return 0;
}

第5題:補充全排列的回溯演算法

題目大意

  1,2,3…9 這九個數字組成一個分數,其值恰好為1/3,如何組法?

題解

  與第3題一樣可以用全排列暴力,但這題是程式碼填空題,且已經手寫一部分全排列演算法,我們負責補充其中一行程式碼。
  寫全排就是用回溯的思想,直接猜到for迴圈裡的第三行,應該就是把第一行的交換操作再交換回來~~複製for裡的第一行程式碼,執行下程式,還不放心就除錯下,看看陣列是不是有按字典序在變化就行了。
  答案:{t=x[k]; x[k]=x[i]; x[i]=t;}
  

第6題:加號改乘號

題目大意

  把1+2+3+…+48+49中的兩個加號改成乘號(修改位置不能相鄰),使得式子的結果由1225變為2015。

題解

  用雙迴圈暴力兩個乘號的位置,計算在數字i、j後的加號改為乘號,式子數值的變化即可,注意j的起始位置為i+2。

#include <stdio.h>

int main() {
    for (int i = 1; i <= 48; i++) {
        for (int j = i + 2; j <= 48; j++) {
            if (1225 - i - (i+1) - j - (j+1) == 
                2015 - i*(i+1) - j*(j+1))
                printf("%d\n", i);
        }
    }
    return 0;
}

  除了輸出的一個10是題目已提示的解,另一個16就是答案。

第7題:牌型種數

題目大意

  去掉大小王的52張撲克牌,不考慮花色及順序,取13張,一共有多少種取法?

題解

  大部分人應該都能想到用13重迴圈暴力解(每層迴圈都是0~4)的方法,當累加和為13時,ans++。
  我在賽中想到的是動態規劃,因為我想到了“小明爬樓梯”,且跟賽前,__M子__給我們講的去年賽題“地宮取寶”的dp思路非常像。
  假設牌是從1到13按順序取的,dp[i][j]表示取到第i號的牌,目前總共有j張牌的取法總數,那麼有狀態轉移方程(注意公式沒考慮邊界處理):
  dp[i][j]=k=j4jdp[i1][k]

#include <iostream>
using namespace std;
typedef long long LL;

LL dp[14][14]; // dp[i][j]: 當前到第i張牌,總共有j張牌時的解的個數

int main() {
    dp[1][0] = dp[1][1] = dp[1][2] = dp[1][3] = dp[1][4] = 1;

    for (int i = 2; i <= 13; i++) {
        for (int k = 0; k <= 13; k++) {
            if (k - 4 >= 0) dp[i][k] += dp[i-1][k-4];
            if (k - 3 >= 0) dp[i][k] += dp[i-1][k-3];
            if (k - 2 >= 0) dp[i][k] += dp[i-1][k-2];
            if (k - 1 >= 0) dp[i][k] += dp[i-1][k-1];
            dp[i][k] += dp[i-1][k];
        }
    }

    cout << dp[13][13] << endl;
    return 0;
}
  答案:3598180   

第8題:計算房子間曼哈頓距離

題目大意

  房子按S形擺放,如
  1 2 3
  6 5 4
  7 8 ……
  現輸入每行的寬度w,計算出任意兩個房子m、n的曼哈頓距離(橫向距離+豎向距離)。

題解

   直接看程式碼

 #include <iostream>
 #include <cmath>
using namespace std;

// 輸入寬度w和房子編號n,返回房子所在行x,列y
void GetPos(int w, int n, int& x, int& y) {
    x = (n-1) / w + 1;
    y = n % w;

    if (y == 0) y = w;
    if (x%2 == 0) { // 偶數行要倒著數
        y = w - y + 1;
    }
}

int main() {
    int w, m, n;
    int x1, y1, x2, y2;

    cin >> w >> m >> n;
    GetPos(w, m, x1, y1);
    GetPos(w, n, x2, y2);

    cout << abs(x1-x2) + abs(y1-y2) << endl;

    return 0;
}

第9題:壘骰子(矩陣快速冪模)

題目大意

   輸入n,m,表示用n個骰子,在m個約束條件下,從下往上疊成一列。接下來m行,每行有兩個數a,b,表示骰子之間數字a、b兩個所在面不能拼接。
   問這n個骰子一共有多少種壘法?答案對1e9+7取餘。

注意骰子擺好後,四個方向均能轉動,即樣例輸入:
   2  1
   1  2
的樣例輸出是:
   544

矩陣快速冪基礎

  請讀者先看這篇文章:hdu5171 GTY’s birthday gift(BestCoder Round #29 1002),或閱讀其他矩陣快速冪模文章,自行補基礎。我給出的這篇文章,寫了個矩陣快速冪模的類模板,將在下面的程式碼中使用。實際解題中,可以根據特殊問題寫特殊程式碼,不必寫出通用的矩陣類,加快解題速度。
  

題解

  比賽中,不使用模板,建議陣列從下標1開始使用。這裡為了和模板配合,所以假設骰子從0開始編號。可以發現規律,如果骰子的a面為0,1,2,那麼a的對面數值就是a+3;相反,如果骰子的a面為3,4,5,那麼a的對面數值就是a-3。這個由當前面變換到對面的操作可以用公式完成:(a+3)%6。
  現在用一個bool陣列isLimit[6][6]來儲存,isLimit[i][j]為真,代表當前骰子i面朝上,不能接j面朝上的骰子。
  矩陣快速冪模,主要是找到A^(n-1) * X中的A和X。那麼這裡的X向量,X[i]初值就是第一個骰子i面朝上的方法數,一共是4種。即X[0]~X[5]均為4。
  A[i][j]就是當前骰子i面朝上,接j面朝上後,原方法數要乘上的因子。故A[i][j]可以由isLimit直接得到。如果i->j被限制,乘因子就是0,否則就是4。

 #include <algorithm>
 #include <cstring>
 #include <iostream>
using namespace std;

/*矩陣類(用於快速冪模)
  1、T是整型型別,N是方陣大小,MOD是取餘的值
  2、Eye是單位陣
*/
template <typename T, const int N, const int MOD>
class Matrix {
    T val[N][N];

public:
    Matrix() { memset(val, 0, sizeof(val)); }
    Matrix(T a[N][N]) { memcpy(val, a, sizeof(val)); }

    Matrix operator*(const Matrix& c) const {
        Matrix res;
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j)
                for (int k = 0; k < N; ++k) {
                    res.val[i][j] += val[i][k] * c.val[k][j];
                    //防止矩陣元素變為負數,若不需要,去掉"+MOD"
                    res.val[i][j] = (res.val[i][j] + MOD) % MOD;
                }
        return res;
    }

    Matrix& operator*=(const Matrix& c) {
        *this = *this * c;
        return *this;
    }

    Matrix operator^(int k) const { //返回*this^k
        Matrix res = Eye();
        Matrix step(*this);
        while (k) {
            if (k & 1) res *= step;
            k >>= 1;
            step *= step;
        }
        return res;
    }

    Matrix Eye() const {
        Matrix a;
        for (int i = 0; i < N; i++) a.val[i][i] = 1;
        return a;
    }

    void out() const {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++)
                cout << val[i][j] << " ";
            cout << "\n";
        }
    }

    T* operator[](int i) { return val[i]; }
};

typedef long long LL;
const int MOD = 1e9 + 7;

int main() {
    int n, m;
    Matrix<LL,6,MOD> A, X, Y;
    bool isLimit[6][6];

    // 輸入處理
    cin >> n >> m;   
    memset(isLimit, false, sizeof(isLimit));
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        isLimit[a-1][ (b+2)%6 ] = true;
        isLimit[b-1][ (a+2)%6 ] = true;
    }

    // 初始化A
    for (int i = 0; i < 6; i++) {
        for (int j = 0; j < 6; j++) {
            A[i][j] = (!isLimit[i][j]) * 4;
        }
    }

    // 初始化X
    for (int i = 0; i < 6; i++) X[i][0] = 4;

    // 計算Y及答案
    Y = (A^(n-1)) * X;
    LL sum = 0;
    for (int i = 0; i < 6; i++) sum += Y[i][0];
    cout << sum % MOD << endl;

    return 0;
}

第10題:樹形DP

題目大意

  在一顆樹中,每個點都有一個權值,有正有負。
  要求找一個連通的點集S,使得累加和最大。

題解

 #include<iostream>
 #include<algorithm>
 #include<cstdio>
 #include<vector>
using namespace std;
typedef long long LL;
const int MAXN=100000+10;
const int INF=-1000000-10;
LL dp[MAXN][2];
vector<int>tree[MAXN];
LL w[MAXN];

void dfs(int p,int fa){
    dp[p][0]=max(dp[p][0],w[p]);
    dp[p][1]=max(dp[p][1],w[p]);
    for(int i=0;i<tree[p].size();i++){
        int son=tree[p][i];
        if(son^fa){
            dfs(son,p);
            dp[p][0]=max(dp[p][0],dp[son][0]);
            if(dp[son][1]>0)dp[p][1]+=dp[son][1];
        }
    }
    dp[p][0]=max(dp[p][0],dp[p][1]);
}

int main(){
    int n,a,b;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>w[i];
    for(int i=1;i<=n;i++){dp[i][0]=INF;dp[i][1]=INF;}
    for(int i=1;i<n;i++){
        cin>>a>>b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }
    dfs(1,-1);
    cout<<dp[1][0]<<"\n";
    return 0;
}