程式語言與程式設計實踐2-> 藍橋杯C/C++備賽記錄1 | 入門瞭解與首周訓練

climerecho發表於2022-03-16

寒假前班主任幫我們報了名,是得好好準備準備。作為一個CSer,coding能力一定不能太弱。我反思,好久沒寫C/C++程式碼了,淨是些隨手寫的python指令碼,剛開始上手題目bug一大堆。

由於也不是啥特別的演算法競賽,就把列入這個系列吧。整理髮出來,也就是一個回顧。

00 賽事瞭解

00-1 oi賽制

每道題提交之後都沒有任何的反饋,每個題都有多個測試點,根據通過的測試點的數量,來給予分數。每道題不限提交次數,如果提交錯誤沒有任何懲罰,僅以最後一次提交為準,比賽過程中看不到實時排名,賽後按照總得分排名。

輸入輸出明確按規定來做。

00-2 基礎語法

要熟悉一門語言的語法,對於我來說就是C/C++,這一點需要再翻翻書,避免一些低階錯誤。

00-3 演算法與資料結構

這一點需要再複習一下自己的資料結構學習筆記,然後再看看網課。確保自己刷題能夠不卡頓。

00-4 刷題

  • 洛谷的官方題單
  • 藍橋杯真題
  • 北京大學的poj
  • acwing
  • 力扣(只有介面函式,不是完全,所以這點不好)

遞迴和暴力需要多練,保證自己有辦法做出來題目。另外再學學貪心、搜尋(深搜極其重要、廣搜一般)、動態規劃。

我此前練了幾十道力扣,今天註冊(0310)了洛谷,做了入門題目,大致瞭解了一下如何把一個題目通過。

關於如何找題,這個知乎博主回答得比較好:

  1. 在左側欄裡的題單廣場裡找,建議先做官方題單,再做使用者分享題單
  2. 在左側欄裡的題庫裡找,要搜哪道題就直接搜就行,要搜哪種題就在高階搜尋裡新增演算法標籤就行,要搜哪種難度的題直接選擇,還有不要只看主題庫裡的題,看看CodeForces,SPOJ,AtCoder,UVA的題都不錯
  3. 還可以看主頁的智慧推薦(不過最近智慧推薦掛了)
  4. 終極辦法(不要經常用),直接發帖求題

01 0310

01-1 P1014 [NOIP1999 普及組] Cantor 表

01-1-1 題目描述

現代數學的著名證明之一是 Georg Cantor 證明了有理數是可列舉的。他是用下面這一張表來證明這一命題的:

1/11/1,1/21/2 , 1/31/3, 1/41/4, 1/51/5, …
2/12/1, 2/22/2 , 2/32/3, 2/42/4, …
3/13/1 , 3/23/2, 3/33/3, …
4/14/1, 4/24/2, …
5/15/1, …
…

我們以 Z 字形給上表的每一項編號。第一項是 1/11/1,然後是 1/21/2,2/12/1,3/13/1,2/22/2,…

輸入格式

整數(1<= N <=10^7)。

輸出格式

表中的第 N 項。

01-1-2 輸入輸出樣例

輸入

7

輸出

1/4

01-1-3 解決程式碼

#include <iostream>
using namespace std;

int main()
{
  int n = 0, sum = 0, i = 1;
  cin >> n;
  while(sum < n)
  {
    ++i;
    //這裡用for在迴圈上不太好,一定要讓i先自增再求和,否則求出來的和少一項
    sum = i*(i - 1)/2;
    //首項公差均為1的等差數列求和,此處的和為第i行為止的Cantor表的總項數
  }
  //總項數與所找項的項數的差
  int sub = sum - n;
  if(i % 2 != 0)
  	//如果i奇數,從左開始數
    cout << i - 1 - sub << "/" << 1 + sub;
  else
    //第i行為偶數行,從右邊數
    cout << 1 + sub<< "/" << i - 1 - sub;
}

01-2 P1035 [NOIP2002 普及組] 級數求和

01-2-1 題目描述

已知:

\[S_n= 1+\frac{1}{2}+\frac{1}{3}+…+\frac{1}{n} \]

顯然對於任意一個整數 k,當 n 足夠大的時候,有Sn>k;現給出一個整數 k,要求計算出一個最小的 n,使得 S> k。

輸入格式

一個正整數 k。

輸出格式

一個正整數 n。

01-2-2 輸入輸出樣例

輸入

1

輸出

2

01-2-3 解決程式碼

這個題沒什麼好說的,唯一值得注意的是C語言除法裡的型別轉換,由於sum是double,如果兩個整數用 ‘/’ 運算子,會得到一個整數,所以得用1./i

#include<iostream>
using namespace std;

int main(){
    int k;
    cin >> k;
    double sum = 0;
    int count = 0,i = 1;
    while(sum <= k){
        sum += 1./i;
        count = i;
        i++;  
    }
    cout << count << endl;
    return 0;
}
//思路很簡單,但最重要的是要注意1./i的資料型別轉換
//改為1/i就會死迴圈

01-3 P1046 [NOIP2005 普及組] 陶陶摘蘋果

01-3-1 題目描述

陶陶家的院子裡有一棵蘋果樹,每到秋天樹上就會結出 10 個蘋果。
蘋果成熟的時候,陶陶就會跑去摘蘋果。
陶陶有個 30 釐米高的板凳,當她不能直接用手摘到蘋果的時候,就會踩到板凳上再試試。

現在已知 10 個蘋果到地面的高度,以及陶陶把手伸直的時候能夠達到的最大高度,
請幫陶陶算一下她能夠摘到的蘋果的數目。假設她碰到蘋果,蘋果就會掉下來。

輸入格式

輸入包括兩行資料。
第一行包含 10 個 100 到 200 之間(包括 100 和 200 )的整數(以釐米為單位)
分別表示 10 個蘋果到地面的高度,
兩個相鄰的整數之間用一個空格隔開。
第二行只包括一個 100 到 120 之間(包含 100 和 120 )的整數(以釐米為單位),
表示陶陶把手伸直的時候能夠達到的最大高度。

輸出格式

輸出包括一行,這一行只包含一個整數,表示陶陶能夠摘到的蘋果的數目。

01-3-2 輸入輸出樣例

輸入

100 200 150 140 129 134 167 198 200 111
110

輸出

5

01-3-3 解決程式碼

一遍過了,就是迴圈 / 判斷結構。

#include<iostream>
using namespace std;

int main(){
    int apple[10]={0};
    for(int i = 0; i < 10; i++){
        cin >> apple[i];
    }
    int height;
    cin >> height;
    int count = 0;
    for(int i = 0; i < 10; i++){
        if((height+30) >= apple[i]){
            count++;
        }
    }
    cout << count;
    return 0;
}
//值得注意的是夠不著會踩板凳  +30

02 0311

02-1 [P1047 NOIP2005 普及組] 校門外的樹

02-1-1 題目描述

某校大門外長度為 L 的馬路上有一排樹,每兩棵相鄰的樹之間的間隔都是 1 米。
我們可以把馬路看成一個數軸,馬路的一端在數軸 0 的位置,另一端在 L 的位置;
數軸上的每個整數點,即 0,1,2,...,L,都種有一棵樹。

由於馬路上有一些區域要用來建地鐵。
這些區域用它們在數軸上的起始點和終止點表示。
已知任一區域的起始點和終止點的座標都是整數,區域之間可能有重合的部分。
現在要把這些區域中的樹(包括區域端點處的兩棵樹)移走。
你的任務是計算將這些樹都移走後,馬路上還有多少棵樹。

輸入格式

第一行有兩個整數,分別表示馬路的長度 L 和區域的數目 m。

接下來 m 行,每行兩個整數 u, v,表示一個區域的起始點和終止點的座標。

輸出格式

輸出一行一個整數,表示將這些樹都移走後,馬路上剩餘的樹木數量。

02-2-2 輸入輸出樣例

輸入

500 3
150 300
100 200
470 471

輸出

298

說明 / 提示 | 資料範圍:

  • 對於 20% 的資料,保證區域之間沒有重合的部分。

  • 對於 100% 的資料,保證:

    \[1 \leq L \leq 10^4 \\ 1 \leq m \leq 100,\\ 0 \leq u \leq v \leq L \]

02-2-3 解決程式碼

這基本就是查表思想,如果樹在拆的範圍內,就給標誌一下,重複拆也是給相同的標誌,最後遍歷輸出樹的陣列,如果沒有標誌,則說明是剩餘下來的。

不過值得注意的程式碼後面我也列出來了:

  • 弄清楚取值範圍,題目中是從0到L,左閉右閉;
  • 提交程式碼要註釋掉測試的程式碼;
#include<iostream>
using namespace std;

int main(){
    int n,m;
    cin >> n >> m;
    int A[n+1]={0};
    // for(int i = 0; i < n; i++){
    //     A[n] = 1;
    // }
    int B[m][2];
    for(int i = 0; i < m; i++){
        for(int j = 0; j < 2; j++){
            cin >> B[i][j];
        }
    }
    // for(int i = 0; i < m; i++){
    //     for(int j = 0; j < 2; j++){
    //         cout << B[i][j]<<endl;
    //     }
    // }
    for(int i = 0; i < m; i++){
            for(int cnt = B[i][0];cnt<=B[i][1];cnt++){
                A[cnt] = 1;
            }
    }
    // for(int i = 0; i < n; i++){
    //     cout << A[i]<<endl;
    // }
    int count = 0;
    for(int i = 0; i <= n; i++){
        //cout << A[i]<<endl;
        if(A[i]==0){
            count++;
        }
    }
    cout << count;
    return 0;
}
//這個題不難,遇到了幾個問題:
1.題目審題,陣列的範圍是什麼,是0到l,左閉右閉
2.提交程式碼,把一些測試用的程式碼沒有註釋掉就交了

02-2 [P1059 NOIP2006 普及組] 明明的隨機數

02-2-1 題目描述

明明想在學校中請一些同學一起做一項問卷調查,
為了實驗的客觀性,他先用計算機生成了N個1到1000之間的隨機整數(N≤100),
對於其中重複的數字,只保留一個,把其餘相同的數去掉,
不同的數對應著不同的學生的學號。
然後再把這些數從小到大排序,按照排好的順序去找同學做調查。
請你協助明明完成“去重”與“排序”的工作。

輸入格式

輸入有兩行,第1行為1個正整數,表示所生成的隨機數的個數N*

第2行有N*個用空格隔開的正整數,為所產生的隨機數。

輸出格式

輸出也是兩行,第1行為1個正整數M,表示不相同的隨機數的個數。

第2行為M*個用空格隔開的正整數,為從小到大排好序的不相同的隨機數。

02-2-2 輸入輸出樣例

輸入

10
20 40 32 67 40 20 89 300 400 15

輸出

8
15 20 32 40 67 89 300 400

02-2-3 解決程式碼

這道題在大體思路上很清晰,但在具體細節上還是有點意思的。所以雖然是一遍過,但是花費時間還是久了點。

具體細節就是:

  1. 排序可以直接排。
  2. 刪除元素我有兩種考慮,一種是雙指標,即一個為主指標,另一個輔助指標以主指標為起點向後探測,如果與主指標值相同則繼續向後,直到不同,再讓主指標直接跳躍過去,這種需要建立一個新陣列來存主指標指到過的值。
  3. 另外一種就是雜湊表了,畢竟也是學過資料結構刷過幾道力扣的人了,雜湊表雖然空間複雜度高一些,但時間上很不錯,思路實現上更簡單。於是最終使用的是雜湊表。
    4.由於對於STL的不熟悉,在原陣列上動刀的事情目前幹不出來。說來也是慚愧,STL當初在一個專案上好好學了,現在卻不能熟練的用出來。
#include<iostream>
#include<algorithm>
using namespace std;

int main(){
    int N;
    cin >> N;
    int A[N];
    for(int i  = 0; i < N; i++){
        cin >> A[i];
    }
    sort(A,A+N);
    // for(int i = 0; i < N; i++){
    //     cout << A[i] << " ";
    // }
    // 排序完成,下面看看能不能刪掉重複的
    int hash[1000]={0};
    for(int i = 0; i < 1000;i++){
        for(int j = 0; j < N; j++){
            if(A[j]==i){
                hash[i]=1;
            }
        }
    }
    //cout << endl;
    int count = 0;
    int result[100]={0};
    int j = 0;
    for(int i = 0; i < 1000; i++){
        if(hash[i]==1){
            //cout << i+1 << " ";
            result[count++]=i;
        }
    }
    cout << count << endl;
    for(int i = 0; result[i]!=0&&i<100;i++){
        cout << result[i]<< " ";
    }
    //雜湊表實現

    //雙指標
    // for(int i = 0; i<N;i++){
    //     for(int j = i+1; j < N;j++){
    //         if(A[j]!=A[i]){
    //             break;
    //         }
    //     }
    // }
    return 0;
}

02-3 [[P1075 [NOIP2012 普及組] 質因數分解 ][https://www.luogu.com.cn/problem/P1075]

02-3-1 題目描述

已知正整數n是兩個不同的質數的乘積,試求出兩者中較大的那個質數。

輸入格式

一個正整數n。

輸出格式

一個正整數p*,即較大的那個質數。

02-3-2 輸入輸出樣例

輸入

21

輸出

7

說明/提示

\[n\le 2\times 10^9 \]

02-3-3 解決程式碼

這道題考察一個基礎的數論知識:

唯一分解定理:一個數能且只能分解為一組質數的乘積。

並且還要明白,題目中的一個正整數,可以被分解為兩個質數對於測試資料的限制,自己就不要打23或是45這種資料來測試,因為根本就不在範圍內

#include<iostream>
using namespace std;

int main(){
    long int num, i;
    cin >> num;
    for( i = 2 ; i * i < num; i++){
        if(num%i==0){
            break;
        }
    }
    cout << num/i <<endl;
}
//實現挺簡單的,可以從num開始--,也可以從2開始++求出小質數再求大質數。

02-4 [P1085 NOIP2004 普及組] 不高興的津津

02-4-1 題目描述

津津上初中了。
媽媽認為津津應該更加用功學習,所以津津除了上學之外,還要參加媽媽為她報名的各科複習班。
另外每週媽媽還會送她去學習朗誦、舞蹈和鋼琴。
但是津津如果一天上課超過八個小時就會不高興,而且上得越久就會越不高興。
假設津津不會因為其它事不高興,並且她的不高興不會持續到第二天。
請你幫忙檢查一下津津下週的日程安排,看看下週她會不會不高興;如果會的話,哪天最不高興。

輸入格式

輸入包括7行資料,分別表示週一到週日的日程安排。
每行包括兩個小於10的非負整數,用空格隔開,
分別表示津津在學校上課的時間和媽媽安排她上課的時間。

輸出格式

一個數字。如果不會不高興則輸出0,
如果會則輸出最不高興的是周幾
(用1, 2, 3, 4, 5, 6, 71,2,3,4,5,6,7分別表示週一,週二,週三,週四,週五,週六,週日)。
如果有兩天或兩天以上不高興的程度相當,則輸出時間最靠前的一天。

02-4-2 輸入輸出樣例

輸入

5 3
6 2
7 2
5 3
5 4
0 4
0 6

輸出

3

02-4-3 解決程式碼以及優化

思路很簡單,甚至我寫起來還會顯得笨手笨腳的(程式碼有點囉嗦)一步一步的太笨重。

值得注意的是最後的tag判斷,如果把放到最外一層的for迴圈內部,最後一組資料會出錯,當時結果出來還有點頭疼,後來意識到是放在迴圈裡可能會導致一些不能預料的結果。

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

int main(){
    int data[7][2],sum[7];
    for(int i = 0 ; i < 7; i++){
        for(int j = 0; j < 2; j++){
            cin >> data[i][j];
        }
    }
    for(int i = 0; i < 7; i++){
        sum[i] = data[i][0] + data[i][1];
    }
    int count = 0, tag = 0;
    for(int i = 0; i < 7; i++){
        if(sum[i]<=8){
            tag++;
            continue;
        }
        else{
            int* temp = max_element(sum,sum+7);
            for(int j = 0; j < 7; j++){
               if(sum[j]==*temp){
                   cout << j+1 <<endl;
                   break;
               }
            }
            break;
        }
    }
    if(tag==7){
            cout << 0 << endl;
    }
    return 0;
}
//奧對,最重點的是使用了一個直接求陣列中最大值的函式max_element,返回值是一個指標
//還是被此前專案函式調包什麼的慣壞了,資料結構老師要是知道了得罵死我。

由於覺得自己寫的實在是太笨重了,我決定看看題解,發現我被陣列牢牢地束縛住了,離開陣列就很難做事情。就比如下面洛谷題解中贊最多的這一個(個人手打,原作者爭持Zill):

#include<iostream>
using namespace std;

int main(){
    int num1,num2, sum;
    int day = 0, max = 0;
    for(int i = 1; i < 8; i++){
        cin >> a >>b;
        sum = num1 + num2;
        if((s > max)&&(sum>8)){
            max = sum;
            day =i;
        }
    }
    cout << day;
    return 0;
}

還有一個作者,直接順序分支結構,確實,題目中明確只有七組數,七個if語句就可以解決。按照複雜度理論,將會是O(1),hhhh。(實際上是用手寫所有if來代替迴圈了。

03 0312 / 0315

事實上這期間只寫了這一道題(除了上一道題在0312也有一些工作,比如看題解),因為有其他的事情,練題計劃中斷了(比如寫Java作業、復變、概率論作業等等)。0312的思路沒寫出來,0315晚上靈機一動,有了另一種解決方案。

03-1 [P1089 NOIP2004 提高組] 津津的儲蓄計劃

津津的零花錢一直都是自己管理。每個月的月初媽媽給津津300300元錢,津津會預算這個月的花銷,並且總能做到實際花銷和預算的相同。

為了讓津津學習如何儲蓄,媽媽提出,津津可以隨時把整百的錢存在她那裡,到了年末她會加上20%還給津津。
因此津津制定了一個儲蓄計劃:每個月的月初,在得到媽媽給的零花錢後,
如果她預計到這個月的月末手中還會有多於100元或恰好100元,她就會把整百的錢存在媽媽那裡,剩餘的錢留在自己手中。

例如11月初津津手中還有83元,媽媽給了津津300元。津津預計1111月的花銷是180元,那麼她就會在媽媽那裡存200元,自己留下183元。到了11月月末,津津手中會剩下3元錢。

津津發現這個儲蓄計劃的主要風險是,存在媽媽那裡的錢在年末之前不能取出。有可能在某個月的月初,津津手中的錢加上這個月媽媽給的錢,不夠這個月的原定預算。如果出現這種情況,津津將不得不在這個月省吃儉用,壓縮預算。

現在請你根據2004年1月到12月每個月津津的預算,判斷會不會出現這種情況。如果不會,計算到2004年年末,媽媽將津津平常存的錢加上20%還給津津之後,津津手中會有多少錢。

輸入格式

12行資料,每行包含一個小於350的非負整數,分別表示1月到12月津津的預算。

輸出格式

一個整數。如果儲蓄計劃實施過程中出現某個月錢不夠用的情況,輸出-X,
X表示出現這種情況的第一個月;
否則輸出到20042004年年末津津手中會有多少錢。

注意,洛谷不需要進行檔案輸入輸出,而是標準輸入輸出。

03-1-2 輸入輸出樣例

輸入1

290
230
280
200
300
170
340
50 
90 
80 
200
60 

輸出1

-7 

輸入2

290 
230 
280 
200 
300 
170 
330 
50 
90 
80 
200 
60 

輸出2

1580

03-1-3 解決程式碼

這道題在說法上比較繞。

第一步,輸入當月預算cost,+上月剩餘last1到剩下的錢last2,300+0-290=10,

第二步,判斷是否>=100:

如果大於,則減去整百部分,

如果小於,繼續一二步。

第三步,如果12個月迴圈完畢一直是小於,那就輸出last+save*1.2

如果>=100後,last取負值,則輸出當月的月份。

上述程式設計的邏輯問題就在一處,即,第三步中的,last取負值的判定,我採用的是last<0,則輸出此前記錄的月份的負值,問題就在於last最後不一定<0

//上面思路走不下去,借鑑題解再來一次
#include<iostream>
using namespace std;
int main(){
    int money = 0, cost[12] = {0}, save = 0, flag = 1, failid = 0;
    for(int i = 0; i < 12; i++){
        cin >> cost[i];
    }
    for(int i = 1; i <= 12; i++){
        money+=300;
        //cin >> cost;
        money-=cost[i-1];
        if(money<0){
            flag = 0;
            //說明已經透支
            failid = i;
            break;
        }
        save+=money/100;//用100的個數代替100
        money%=100;//存款後剩餘的前
        //與我最初的思路相差在於,我冗餘了一個判斷是否超過100的語句
        //事實上money不超過100的話,%100還是它自己。

    }
    if(flag==1){
        money+=save*120;
        cout << money;
    }
    else{
        cout <<-failid;
    }
    return 0;
}

相關文章