1. 程式人生 > >2018 第九屆藍橋杯省賽總結 + 解題報告(C語言B組)

2018 第九屆藍橋杯省賽總結 + 解題報告(C語言B組)

2018/4/1,我參加了藍橋杯C語言B組湖南省賽,今年第三次參加藍橋杯了,雖然會的東西比去年多了不少,但是發揮卻不如上次,最大的總結就是要細心啊!

1.第幾天

2000年的1月1日,是那一年的第1天。
那麼,2000年的5月4日,是那一年的第幾天?


算日期嘛,這種題做的不能再熟了,還是這麼簡單的這種,結果一上來看到閏年直接把2月份算作28天。。
正解:125

2.明碼

漢字的字形存在於字型檔中,即便在今天,16點陣的字型檔也仍然使用廣泛。
16點陣的字型檔把每個漢字看成是16x16個畫素資訊。並把這些資訊記錄在位元組中。

一個位元組可以儲存8位資訊,用32個位元組就可以存一個漢字的字形了。
把每個位元組轉為2進製表示,1表示墨跡,0表示底色。每行2個位元組,
一共16行,佈局是:

第1位元組,第2位元組
第3位元組,第4位元組
....
第31位元組, 第32位元組

這道題目是給你一段多個漢字組成的資訊,每個漢字用32個位元組表示,這裡給出了位元組作為有符號整數的值。

題目的要求隱藏在這些資訊中。你的任務是復原這些漢字的字形,從中看出題目的要求,並根據要求填寫答案。

這段資訊是(一共10個漢字):

4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 
16 64 16 64 34 68 127 126 66 -124 67 4 66 4 66 -124 126 100 66 36 66 4 66 4 66 4 126 4 66 40 0 16 
4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 
0 -128 64 -128 48 -128 17 8 1 -4 2 8 8 80 16 64 32 64 -32 64 32 -96 32 -96 33 16 34 8 36 14 40 4 
4 0 3 0 1 0 0 4 -1 -2 4 0 4 16 7 -8 4 16 4 16 4 16 8 16 8 16 16 16 32 -96 64 64 
16 64 20 72 62 -4 73 32 5 16 1 0 63 -8 1 0 -1 -2 0 64 0 80 63 -8 8 64 4 64 1 64 0 -128 
0 16 63 -8 1 0 1 0 1 0 1 4 -1 -2 1 0 1 0 1 0 1 0 1 0 1 0 1 0 5 0 2 0 
2 0 2 0 7 -16 8 32 24 64 37 -128 2 -128 12 -128 113 -4 2 8 12 16 18 32 33 -64 1 0 14 0 112 0 
1 0 1 0 1 0 9 32 9 16 17 12 17 4 33 16 65 16 1 32 1 64 0 -128 1 0 2 0 12 0 112 0 
0 0 0 0 7 -16 24 24 48 12 56 12 0 56 0 -32 0 -64 0 -128 0 0 0 0 1 -128 3 -64 1 -128 0 0 

這道題還挺有意思的,按照題意把漢字輸出出來就可以了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <map>
#include <string>
#include <cmath>
#define mem(a,b) memset(a,b,sizeof(a))

using namespace std;

string f(int
n) { string res; for(int i=0;i<8;i++) { if(n%2) res+="#"; else res+=" "; n/=2; } reverse(res.begin(),res.end()); return res; } int a[15][40]; int main() { for(int i=0;i<10;i++) { for(int j=0;j<32;j++) { cin>>a[i][j]; } } for(int i=0;i<10;i++) { for(int j=0;j<32;j++) { cout<<f(a[i][j]); if(j%2) cout<<endl; } cout<<endl; } return 0; }

這裡寫圖片描述
控制檯看到九的九次方等於多少?
再用計算器算一下就得了答案387420489
聽說有人寫81?

3.乘積尾零

如下的10行資料,每行有10個整數,請你求出它們的乘積的末尾有多少個零?

5650 4542 3554 473 946 4114 3871 9073 90 4329
2758 7949 6113 5659 5245 7432 3051 4434 6704 3594
9937 1173 6866 3397 4759 7557 3070 2287 1453 9899
1486 5722 3135 1170 4014 5510 5120 729 2880 9019
2049 698 4582 4346 4427 646 9742 7340 1230 7683
5693 7015 6887 7381 4172 4341 2909 2027 7355 5649
6701 6645 1671 5978 2704 9926 295 3125 3878 6785
2066 4247 4800 1578 6652 4616 1113 6205 3264 2915
3966 5291 2904 1285 2193 1428 2265 8730 9436 7074
689 5510 8243 6114 337 4096 8199 7313 3685 211


這道題說起來也容易,算2和5的數量就行了。。但是當時可能是沒帶腦子吧,就只算了一個
#include <iostream>  
#include <cstdio>  
#include<cmath>
#include<algorithm>
using namespace std;
int main()
{
    int n,c2=0,c5=0;
    for (int i = 0; i < 100; i++)
    {
        cin >> n;
        while (n % 5 == 0)
        {
            n /= 5;
            c5++;
        }
        while (n % 2 == 0)
        {
            n /= 2;
            c2++;
        }
    }
    cout << min(c2, c5) << endl;
    return 0;
}

輸出31

4.測試次數

x星球的居民脾氣不太好,但好在他們生氣的時候唯一的異常舉動是:摔手機。
各大廠商也就紛紛推出各種耐摔型手機。x星球的質監局規定了手機必須經過耐摔測試,並且評定出一個耐摔指數來,之後才允許上市流通。

x星球有很多高聳入雲的高塔,剛好可以用來做耐摔測試。塔的每一層高度都是一樣的,與地球上稍有不同的是,他們的第一層不是地面,而是相當於我們的2樓。

如果手機從第7層扔下去沒摔壞,但第8層摔壞了,則手機耐摔指數=7。
特別地,如果手機從第1層扔下去就壞了,則耐摔指數=0。
如果到了塔的最高層第n層扔沒摔壞,則耐摔指數=n

為了減少測試次數,從每個廠家抽樣3部手機參加測試。

某次測試的塔高為1000層,如果我們總是採用最佳策略,在最壞的運氣下最多需要測試多少次才能確定手機的耐摔指數呢?

請填寫這個最多測試次數。


沒看到只能用三部手機,直接二分寫了個10。
由於只有三部手機,摔壞一臺之後這臺就無法再用來做測試了,所以設計的演算法必須滿足最多三臺手機壞掉時能確定耐摔指數。
正解應該是先分9組,即100,200,300…900,假如200沒摔壞,到300摔壞了,那麼就在201~299之間測試,也就是99層,再分9組,210,220,230…290,同理,如果220沒摔壞,230摔壞了,那就在221到229中逐個測試,如果第222層摔壞了,223層沒摔壞,就可以確定耐摔指數為222。
按照這樣的方法,可以用三臺手機確定1~999層摔壞的臨界點,最壞次數為9x3=27。如果前999層都沒摔壞,那就在1000層再試一次,那麼答案就是28。
但是看到有大佬用dp算出來是19,所以我也還不確定。

5.快速排序

以下程式碼可以從陣列a[]中找出第k小的元素。

它使用了類似快速排序中的分治演算法,期望時間複雜度是O(N)的。

請仔細閱讀分析原始碼,填寫劃線部分缺失的內容。

#include <stdio.h>

int quick_select(int a[], int l, int r, int k) {
    int p = rand() % (r - l + 1) + l;
    int x = a[p];
    {int t = a[p]; a[p] = a[r]; a[r] = t;}
    int i = l, j = r;
    while(i < j) {
        while(i < j && a[i] < x) i++;
        if(i < j) {
            a[j] = a[i];
            j--;
        }
        while(i < j && a[j] > x) j--;
        if(i < j) {
            a[i] = a[j];
            i++;
        }
    }
    a[i] = x;
    p = i;
    if(i - l + 1 == k) return a[i];
    if(i - l + 1 < k) return quick_select( _____________________________ ); //填空
    else return quick_select(a, l, i - 1, k);
}

int main()
{
    int a[] = {1, 4, 2, 8, 5, 7, 23, 58, 16, 27, 55, 13, 26, 24, 12};
    printf("%d\n", quick_select(a, 0, 14, 5));
    return 0;
}

程式碼缺了標頭檔案stdlib.h所以有人說編譯報錯
第k大數是演算法課上學過的內容,但是我還是做錯了。(哭)
a,i+1,r,k-(i - l + 1)
思想和快排一樣,在l到r間隨機選一個數i,然後把比它小的數放到前面,比它大的數放到後面,這時候看i的值,如果是l到r間的第k大數,則說明找到了,否則就在前面或後面繼續找。這裡的i-l-1指的是i在l到r間排第幾位

6.遞增三元組

給定三個整數陣列
A = [A1, A2, … AN],
B = [B1, B2, … BN],
C = [C1, C2, … CN],
請你統計有多少個三元組(i, j, k) 滿足:
1. 1 <= i, j, k <= N
2. Ai < Bj < Ck

【輸入格式】
第一行包含一個整數N。
第二行包含N個整數A1, A2, … AN。
第三行包含N個整數B1, B2, … BN。
第四行包含N個整數C1, C2, … CN。

對於30%的資料,1 <= N <= 100
對於60%的資料,1 <= N <= 1000
對於100%的資料,1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000

【輸出格式】
一個整數表示答案

【樣例輸入】
3
1 1 1
2 2 2
3 3 3

【樣例輸出】
27


暴力是不可能暴力的,今年難度確實比以前高了啊。。
我的方案是把A和C陣列排序,遍歷B陣列,對每個Bi二分找A中有多少個比它小的,C中有多少個比它大的,相乘後求和。時間複雜度:O(NlogN)
#include <iostream>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int maxn = 100000 + 5;
typedef long long LL;
int a[maxn],b[maxn],c[maxn];
int n;
int bs1(int x)//在a中找最大的比x小的值的下標
{
    int lo = 0, hi = n-1, ans = -1,mid;
    while (lo <= hi)
    {
        mid = (hi - lo) / 2 + lo;
        if (a[mid] < x)
        {
            ans = max(ans, mid);
            lo = mid + 1;
        }
        else
            hi = mid - 1;
    }
    return ans;
}

int bs2(int x)//在c中找最小的比x大的值的下標
{
    int lo = 0, hi = n-1, ans = n, mid;
    while (lo <= hi)
    {
        mid = (hi - lo) / 2 + lo;
        if (c[mid] > x)
        {
            ans = min(ans, mid);
            hi = mid - 1;
        }
        else
            lo = mid + 1;
    }
    return ans;
}


int main()
{

    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    for (int i = 0; i < n; i++)
        cin >> b[i];
    for (int i = 0; i < n; i++)
        cin >> c[i];
    sort(a, a + n);
    sort(c, c + n);
    LL ans = 0;
    for (int i = 0; i < n; i++)
    {
        LL num1 = bs1(b[i]) + 1;
        LL num2 = n - bs2(a[i]);
        ans += num1 * num2;
    }
    cout << ans << endl;
    return 0;
}

7.標題:螺旋折線

這裡寫圖片描述
如圖p1.png所示的螺旋折線經過平面上所有整點恰好一次。
對於整點(X, Y),我們定義它到原點的距離dis(X, Y)是從原點到(X, Y)的螺旋折線段的長度。

例如dis(0, 1)=3, dis(-2, -1)=9

給出整點座標(X, Y),你能計算出dis(X, Y)嗎?

【輸入格式】
X和Y

對於40%的資料,-1000 <= X, Y <= 1000
對於70%的資料,-100000 <= X, Y <= 100000
對於100%的資料, -1000000000 <= X, Y <= 1000000000

【輸出格式】
輸出dis(X, Y)

【樣例輸入】
0 1

【樣例輸出】
3


x和y的範圍是1e9,只能是規律題了,然而智商太低想了好久也沒看出公式。。。
我的方法是先分層,在看這個點是這層的第幾個
這裡寫圖片描述
第n層的起點為(-n,-n+1),終點為(-n,-n),每層的數量是8*n,所以前n層的總數就用等差數列求和公式算咯
這裡寫圖片描述
按照這個方法列印前6層沒問題,稍微注意下用long long的地方就可以了,複雜度為O(1)
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long LL;
LL n;
LL cnt(int x, int y)//看這個點是第n層的第幾個
{
    if (x == -n)
    {
        if (y == -n)
            return 8 * n;
        else
            return n + y;
    }
    if (y == n)
    {
        return 3 * n + x;
    }
    if (x == n)
    {
        return 5 * n - y;
    }
    if (y == -n)
    {
        return 7 * n - x;
    }
}
int main()
{
    int x, y;
    cin >> x >> y;
    n = max(abs(x), abs(y));
    LL ans = (n - 1)*n * 4 +cnt(x, y);
    cout << ans << endl;
    return 0;
}

8.日誌統計

小明維護著一個程式設計師論壇。現在他收集了一份”點贊”日誌,日誌共有N行。其中每一行的格式是:

ts id

表示在ts時刻編號id的帖子收到一個”贊”。

現在小明想統計有哪些帖子曾經是”熱帖”。如果一個帖子曾在任意一個長度為D的時間段內收到不少於K個贊,小明就認為這個帖子曾是”熱帖”。

具體來說,如果存在某個時刻T滿足該帖在[T, T+D)這段時間內(注意是左閉右開區間)收到不少於K個贊,該帖就曾是”熱帖”。

給定日誌,請你幫助小明統計出所有曾是”熱帖”的帖子編號。

【輸入格式】
第一行包含三個整數N、D和K。
以下N行每行一條日誌,包含兩個整數ts和id。

對於50%的資料,1 <= K <= N <= 1000
對於100%的資料,1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000

【輸出格式】
按從小到大的順序輸出熱帖id。每個id一行。

【輸入樣例】
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3

【輸出樣例】
1
3


模擬題,看上去是有些麻煩,要先對輸入的日誌根據時間排序,然後再給每個帖子一個個把點贊時間加上去,每次看看是否已經滿足熱帖條件(二分找區間內最小下標),若滿足就記錄,複雜度O(NlogN)
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <set>
#include <vector>
using namespace std;

const int maxn = 100000 + 5;
struct Node
{
    int ts, id;
};

bool cmp(Node p, Node q)
{
    return p.ts < q.ts;
}
Node a[maxn];
vector<int> t[maxn];
set<int> ans;
int n, k, d;
int bs(int i)//二分找值在區間[T-D,T]內最小下標
{
    int m = t[i][t[i].size() - 1];
    int lo = 0, hi = t[i].size() - 1, mid, res = t[i].size();
    while (lo <= hi)
    {
        mid = (hi + lo) / 2;
        if (t[i][mid] >= m - d)
        {
            res = min(res, mid);
            hi = mid - 1;
        }
        else
            lo = mid + 1;
    }
    return res;
}

int main()
{
    cin >> n >>d >> k;
    d--;//[T,T+D) => [T,T+D-1]
    for (int i = 0; i < n; i++)
        cin >> a[i].ts >> a[i].id;
    sort(a, a + n, cmp);
    for (int i = 0; i < n; i++)
    {
        t[a[i].id].push_back(a[i].ts);
        int cnt = t[a[i].id].size() - bs(a[i].id);
        if (cnt >= k)
            ans.insert(a[i].id);
    }
    for (set<int>::iterator i = ans.begin(); i != ans.end(); i++)
    {
        cout << *i << endl;
    }
    return 0;
}

8.全球變暖

你有一張某海域NxN畫素的照片,”.”表示海洋、”#”表示陸地,如下所示:

…….
.##….
.##….
….##.
..####.
…###.
…….

其中”上下左右”四個方向上連在一起的一片陸地組成一座島嶼。例如上圖就有2座島嶼。

由於全球變暖導致了海面上升,科學家預測未來幾十年,島嶼邊緣一個畫素的範圍會被海水淹沒。具體來說如果一塊陸地畫素與海洋相鄰(上下左右四個相鄰畫素中有海洋),它就會被淹沒。

例如上圖中的海域未來會變成如下樣子:

…….
…….
…….
…….
….#..
…….
…….

請你計算:依照科學家的預測,照片中有多少島嶼會被完全淹沒。

【輸入格式】
第一行包含一個整數N。 (1 <= N <= 1000)
以下N行N列代表一張海域照片。

照片保證第1行、第1列、第N行、第N列的畫素都是海洋。

【輸出格式】
一個整數表示答案。

【輸入樣例】
7
…….
.##….
.##….
….##.
..####.
…###.
…….

【輸出樣例】
1


最慘的一道。。題目問被淹沒的數量,我輸出沒被淹沒的數量。
待補