1. 程式人生 > 其它 >第十一屆藍橋杯第三場軟體類省賽 C++ B組

第十一屆藍橋杯第三場軟體類省賽 C++ B組

第十一屆藍橋杯第三場軟體類省賽 C++ B組

試題 A: 數青蛙

【問題描述】

​ “一隻青蛙一張嘴,兩隻眼睛四條腿。兩隻青蛙兩張嘴,四隻眼睛八條腿。三隻青蛙三張嘴,六隻眼睛十二條腿。……二十隻青蛙二十張嘴,四十隻眼睛八十條腿。”

請問上面這段文字,如果完全不省略,全部寫出來,從 1 到 20 只青蛙,總共有多少個漢字。

約定:數字 2 單獨出現讀成 “兩”,在其他數裡面讀成 “二”,例如 “十二”。10 讀作 “十”,11 讀作 “十一”,22 讀作 “二十二”。請只計算漢字的個數,標點符號不計算。

【解題思路】

​ 1~10是一個漢字;

​ 11~19以及10的倍數是兩位;

​ 其餘情況三位;

#include <bits/stdc++.h>
using namespace std;
int f(int x){
	if(x <= 10)return 1;
	if(x % 10 == 0)return 2;
	if(x <= 20)return 2;
	return 3;
}
int main(){
	int ans = 200;
	for(int i = 1;i <= 20;i++){
		int a = i;
		int b = i + i;
		int c = i * 4;
		ans += f(a) * 2;
		ans += f(b) + f(c);
	}
	cout << ans << endl;
	return 0;
	//答案 353
}

試題 B: 互質

【問題描述】

​ 今年是 2020 年,今天是 10 月 18 日。
​ 請問在 1 到 2020 中,有多少個數與 1018 互質。

【解題思路】

#include <bits/stdc++.h>
using namespace std;
int main(){
	int ans = 0;
	for(int i = 1;i <= 2020;i++){
		ans += __gcd(i,1018) == 1;
	}
	cout << ans << endl;
	//答案1008
	return 0;
}

試題 C: 車牌

【問題描述】

​ A 市的車牌由六位組成,其中前三位可能為數字 0 至 9,或者字母 A 至 F,每位有 16 種可能。後三位只能是數字 0 至 9。為了減少攀比,車牌中不能有連續三位是相同的字元。

​ 例如,202020 是合法的車牌,AAA202 不是合法的車牌,因為前三個字母相同。

請問,A 市有多少個合法的車牌?

【解題思路】

​ 總共情況為16*16*16*10*10*10

​ 第一位第二位第三位字元一致,可以看做一位,有16種情況,第四位第五位第六位分別有10種情況

​ 其他位上也類似,減去之後就是答案

#include <bits/stdc++.h>
using namespace std;
int main() {
    int ans = 16 * 16 * 16 * 10 * 10 * 10;
    ans -= 16 * 10 * 10 * 10;
    ans -= 16 * 10 * 10 * 10;
    ans -= 16 * 16 * 10 * 10;
    ans -= 16 * 16 * 16 * 10;
    cout << ans << endl;
    //答案 3997440
    return 0;
}

試題 D: Fibonacci 集合

【問題描述】

​ 小藍定義了一個 Fibonacci 集合 F,集合的元素如下定義:

  1. 最小的 5 個 Fibonacci 數 1, 2, 3, 5, 8 屬於集合 F。

  2. 如果一個元素 x 屬於 F,則 3x + 2、5x + 3 和 8x + 5 都屬於集合 F。

  3. 其他元素都不屬於 F。

​ 請問,這個集合中的第 2020 小元素的值是多少?

【解題思路】

​ 優先佇列直到pop出2020個元素

#include <bits/stdc++.h>
using namespace std;
int main() {
    set<int> s;
    priority_queue<int, vector<int>, greater<int>> q;
    q.push(1);q.push(2);q.push(3);q.push(5);q.push(8);
    s.insert(1);s.insert(2);s.insert(3);s.insert(5);s.insert(8);
    int cnt = 0;
    while(q.size()) {
        cnt ++;
        int x = q.top();
        q.pop();
        if(cnt == 2020) {
            cout << x << endl;
            break;
        }
        int w[] = {3 * x + 2, 5 * x + 3, 8 * x + 5};
        for (int i = 0; i < 3; i ++)
            if(!s.count(w[i])) {
                s.insert(w[i]);
                q.push(w[i]);
            }
    }
    //答案: 41269
    return 0;
}

試題 E: 上升子串

【問題描述】

​ 小藍有一個字母矩陣,他喜歡和小夥伴們在這個矩陣上玩一些遊戲。今天,他打算玩找上升子串的遊戲。遊戲是合作性質的。小藍和小夥伴們首先要在矩陣中指定一個位置,然後從這個位置開始,向上下左右相鄰位置移動,移動必須滿足所到達位置上的字母比當前位置大。小藍和小夥伴們可以移動任意多次,也可以隨時停下來,這樣就找到了一個上升子串。只要子串在矩陣中的位置不同,就認為是不同的子串。

​ 小藍想知道,一共可以找到多少個上升子串。小藍的矩陣很大,已經放在了試題目錄下面,叫 inc.txt。為了更清楚的

【解題思路】

​ 記憶化搜尋,使用$$f[i][j]$$來表示從i開始到j結束的路徑條數,接下來需要dfs來計算即可

#include <iostream>
#include <vector>
using namespace std;
const int maxn = 110;
vector<string> g;
int f[maxn][maxn]; // f[i][j]: 表示以[i,j]開頭的上升子序列
int n, m;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int dfs(int x, int y) { // dfs返回以[x,y]開頭的上升子序列
    if(f[x][y]) return f[x][y];
    f[x][y] = 1;
    for(int i = 0; i < 4; i ++ ) {
        int nx = x + dx[i], ny = y + dy[i];
        if(nx >= 0 && nx < n && ny >= 0 && ny < m && g[nx][ny] > g[x][y]) {
            f[x][y] += dfs(nx, ny);
        }
    }
    return f[x][y];
}

int main() {
    freopen("inc.txt", "r", stdin);
    string s;
    while(getline(cin, s)){
    	g.push_back(s);
    }
    n = g.size(), m = g[0].size();
    int res = 0;
    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < m; j ++ ) {
            res += dfs(i, j);
        }

    cout << res << endl;
    return 0;
}

試題 F: 日期識別

【問題描述】

​ 小藍要處理非常多的資料,其中有一些資料是日期。在小藍處理的日期中有兩種常用的形式:英文形式和數字形式。英文形式採用每個月的英文的前三個字母作為月份標識,後面跟兩位數字表示日期,月份標識第一個字母大寫,後兩個字母小寫,日期小於 10 時要補前導 0。1 月到 12 月英文的前三個字母分別是 Jan、Feb、Mar、Apr、May、Jun、Jul、Aug、Sep、Oct、Nov、Dec。數字形式直接用兩個整數表達,中間用一個空格分隔,兩個整數都不寫前導 0。其中月份用 1 至 12 分別表示 1 月到 12 月。

​ 輸入一個日期的英文形式,請輸出它的數字形式。

【樣例輸入】

Feb08

【樣例輸出】

02 08

【解題思路】

​ substr取前三個字元用來判斷是幾月份即可

#include <bits/stdc++.h>
using namespace std;
int main() {
    string months[13] = {"", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    string s;
    cin >> s;
    string month, day;
    month = s.substr(0, 3);
    day = s.substr(3);
    for(int i = 1; i < 13; i ++) {
        if(month == months[i]) cout << i << ' ';
    }
    cout << stoi(day);
    return 0;
}

試題 G: 乘法表

【問題描述】

​ 九九乘法表是學習乘法時必須要掌握的。在不同進位制數下,需要不同的乘法表。
​ 例如,四進位制下的乘法表如下所示:

1*1=1
2*1=2 2*2=10
3*1=3 3*2=12 3*3=21

​ 請注意,乘法表中兩個數相乘的順序必須為樣例中所示的順序,不能隨意
​ 交換兩個乘數。
​ 給定 P,請輸出 P 進位制下的乘法表。

【樣例輸入】

8

【樣例輸出】

1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=11
4*1=4 4*2=10 4*3=14 4*4=20
5*1=5 5*2=12 5*3=17 5*4=24 5*5=31
6*1=6 6*2=14 6*3=22 6*4=30 6*5=36 6*6=44
7*1=7 7*2=16 7*3=25 7*4=34 7*5=43 7*6=52 7*7=61

【解題思路】

​ 簡單的進位制模擬,注意一個十進位制轉n進位制不要寫錯

#include <bits/stdc++.h>
using namespace std;
char f(int x){
	if(x <= 9 && x >= 0)return x + '0';
	return x - 10 + 'A';
}
int r;
string DecToR(int x){
	string ans = "";
	while(x){
		ans += f(x % r);
		x /= r;
	}
	reverse(ans.begin(),ans.end());
	return ans;
}
int main() {
    cin >> r;
    for(int i = 1;i < r;i++){
    	for(int j = 1;j <= i;j++){
    		cout << DecToR(i) << "*" << DecToR(j) << "=" << DecToR(i * j) << " ";
    	}
    	cout << '\n';
    }
    return 0;
}

試題 H: 限高杆

【問題描述】

​ 某市有 n 個路口,有 m 段道路連線這些路口,組成了該市的公路系統。其中一段道路兩端一定連線兩個不同的路口。道路中間不會穿過路口。由於各種原因,在一部分道路的中間設定了一些限高杆,有限高杆的路段貨車無法通過。在該市有兩個重要的市場 A 和 B,分別在路口 1 和 n 附近,貨車從市場 A出發,首先走到路口 1 ,然後經過公路系統走到路口 n,才能到達市場 B。

​ 兩個市場非常繁華,每天有很多貨車往返於兩個市場之間。市長髮現,由於限高杆很多,導致貨車可能需要繞行才能往返於市場之間,這使得貨車在公路系統中的行駛路程變長,增加了對公路系統的損耗,增加了能源的消耗,同時還增加了環境汙染。市長決定要將兩段道路中的限高杆拆除,使得市場 A 和市場 B 之間的路程變短。請問最多能減少多長的距離?

【輸入格式】

​ 輸入的第一行包含兩個整數 n, m,分別表示路口的數量和道路的段數。
​ 接下來 m 行,每行四個整數 a, b, c, d,表示路口 a 和路口 b 之間有一段長度為 c 的道路。如果 d 為 0,表示這段道路上沒有限高杆;如果 d 為 1,表示這段道路上有限高杆。兩個路口之間可能有多段道路。
輸入資料保證在不拆除限高杆的情況下,貨車能通過公路系統從路口 1 正常行駛到路口 n。

【輸出格式】

​ 輸出一行,包含一個整數,表示拆除兩段道路的限高杆後,市場 A 和市場B 之間的路程最大減少多長距離。

【樣例輸入】

5 7
1 2 1 0
2 3 2 1
1 3 9 0
5 3 8 0
4 3 5 1
4 3 9 0
4 5 4 0

【樣例輸出】

6

【解題思路】

​ 使用spfa來求解此題,但是假設我們列舉所有斷點的話,那麼很明顯會超時,所以需要對其進行改進,拓展dist為二維使用\(dist[n][i]\)來表示從1號點走到n號點,經過限高杆為k個的最短路

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 10010, M = 200010;
int n, m;
bool vis[N][3];
int dist[N][3];
int h[N], e[M], s[M], w[M], ne[M], idx;
void add(int a, int b, int c, int d) {
    e[idx] = b, w[idx] = c, s[idx] = d, ne[idx] = h[a], h[a] = idx ++;
}

void spfa() {
    queue<pii> q;
    memset(dist, 0x3f, sizeof dist);
    q.push({1, 0});
    dist[1][0] = dist[1][1] = dist[1][2] = 0;
    vis[1][0] = true;
    while(q.size()) {
        int u = q.front().first;
        int tcnt = q.front().second;
        q.pop();
        vis[u][tcnt] = false;
        for (int i = h[u]; i != -1; i = ne[i]) {
            int v = e[i];
            int total = tcnt + s[i];
            if(total > 2) continue;
            if(dist[v][total] > dist[u][tcnt] + w[i]) {
                dist[v][total] = dist[u][tcnt] + w[i];
                if(!vis[v][total]) {
                    vis[v][total] = true;
                    q.push({v, total});
                }
            }
        }
    }
}

int main() {
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while(m --) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        add(a, b, c, d), add(b, a, c, d);
    }
    spfa();
    cout << dist[n][0] - min(dist[n][1], dist[n][2]) << endl;
    return 0;
}

試題 I: 畫中漂流

【問題描述】

​ 在夢境中,你踏上了一隻木筏,在江上漂流。根據對當地的瞭解,你知道在你下游 D 米處有一個峽谷,如果你向下遊前
進大於等於 D 米則必死無疑。現在你打響了急救電話,T 秒後救援隊會到達並將你救上岸。水流速度是1 m/s,你現在有 M 點體力。每消耗一點體力,你可以劃一秒槳使船向上遊前進 1m,否則會向下遊前進 1m (水流)。M 點體力需在救援隊趕來前花光。因為江面太寬了,憑藉你自己的力量不可能上岸。請問,有多少種划槳的方案可以讓你得救。

​ 兩個划槳方案不同是指:存在某一秒鐘,一個方案划槳,另一個方案不劃。

【樣例輸入】

1 6 3

【樣例輸出】

5

【解題思路】

​ 比較簡單的動態規劃題,你可以在每一秒中選擇劃或者不劃的兩種操作,通過計算剩餘的體力和時間可以計算出是否已經落下懸崖,加上兩面的合法方案,既可以將答案輸出

具體方案為

// dp[i,j] : 花費時間為i,且剩餘體力為j的方案數 答案:dp[t][0],初始化f[0][m] = 1;
// 不劃f[i - 1][j] , 劃f[i - 1][j + 1];
// 關鍵點:距離可以通過 時間和剩餘體力計算出來!
// 初始距離:d, 向上遊距離:m - j ,向下流距離 i - (m - j)
#include <bits/stdc++.h>
using namespace std;
const int N = 3010, mod = 1e9 + 7;
int d, t, m;
int dp[N][N];
int main() {
    cin >> d >> t >> m;
    dp[0][m] = 1;
    for(int i = 1; i <= t; i ++ )
        for(int j = 0; j <= m; j ++) {
            int dist = d + (m - j) - (i - (m - j));
            if(dist > 0) {
                dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;// 不劃
                dp[i][j] = (dp[i][j] + dp[i - 1][j + 1]) % mod;// 劃
            }
        }
    cout << dp[t][0] << endl;
    return 0;
}

試題 J: 旅行家

【問題描述】

​ 從前,在海上有 n 個島嶼,編號 1 至 n。居民們深受洋流困擾,無法到達比自己當前所在島嶼編號更小的島嶼。經過數年以後,島嶼上的人數隨著島嶼的編號遞增(可能相等)。作為一名出色的旅行家(RP 學家),你想從 1 號島嶼出發開啟一次旅程,以獲得更多的 RP,因為受到海洋的洋流影響,你只能去到比當前島嶼編號更大的島嶼。因為你比較善良,你會在離開一個島嶼的時候將你的 RP 分散給島民,具體的:你的 RP 會除以 2(用去尾法取整,或者說向零取整)(當你的 RP 小於零時,島民也依舊要幫你分擔,畢竟你們已經建立起了深厚的友誼)。第 i 號島嶼有 Ti 人, 但是你很挑剔,每次你從 j 號島嶼到達 i 號島嶼時,你只會在到達的島嶼上做 Ti × T j 件好事(一件好事可以獲得 1 點 RP)。

​ 唯一不足的是,由於你在島上住宿,勞民傷財,你會扣除巨量 RP,第 i 號島嶼的住宿扣除 Fi 點 RP。注意:將離開一個島嶼時,先將 RP 扣除一半,再扣除住宿的 RP,最後在新到達的島嶼上做好事,增加 RP。離開 1 號島嶼時需要扣除在 1 號島嶼住宿的 RP,當到達這段旅程的最後一個島嶼上時,要做完好事,行程才能結束,也就是說不用扣除在最後到達的島嶼上住宿的 RP。你因為熱愛旅行 (RP),所以從 1 號島嶼開始旅行,初始時你有 0 點 RP。

​ 你希望選擇一些島嶼經過,最終選擇一個島嶼停下來,求最大的 RP 值是多少?

【樣例輸入】

3
4 4 5
1 10 3

【樣例輸出】

19

【解題思路】

​ 類似於spfa的做法,當前點與比他大的所有點都有連邊的spfa做法可以較為輕鬆的獲得大多數的分數

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 500010;
int n;
int T[maxn], F[maxn],rp[maxn];
bool st[maxn];
signed main() {
    cin >> n;
    for(int i = 1; i <= n; i ++ ) cin >> T[i];
    for(int i = 1; i <= n; i ++ ) cin >> F[i];
    memset(rp, -0x3f, sizeof rp);
    rp[1] = 0;
    queue<int> q;
    q.push(1);
    st[1] = true;
    while(q.size()) {
        int t = q.front();
        q.pop();
        st[t] = false;
        for(int i = t + 1; i <= n; i ++) {
            if(rp[t] / 2 - F[t] + T[t] * T[i] > rp[i]) {
                rp[i]  =  rp[t] / 2 - F[t] + T[t] * T[i];
                if(!st[i]) {
                    q.push(i);
                    st[i] = true;
                }
            }
        }
    }
    int res = -1e18;
    for(int i = 1; i <= n; i ++) res = max(res, rp[i]);
    printf("%lld\n", res);
    return 0;
}