1. 程式人生 > >解密計算機與資訊學競賽

解密計算機與資訊學競賽

身邊有很多人會問我這個學習資訊競賽還不到一年的OIer:

“你們計算機學什麼啊?”

“你們怎麼判斷得分啊?”

“你們是不是天天在機房玩遊戲啊?”

......

受到之前看到過的一篇文章的啟發: 《有趣又有用的資訊學競賽》

而且,幾乎所有人以後都會學計算機(據我所知現在已經有其他競賽的大佬在學了)。

我決定修改一下原文,給大家的計算機入個門,把資訊學競賽介紹一下。

計算機到底在學什麼亂七八糟的東西?

簡而言之:程式設計。

程式設計主要分為三個部分:

1.語言。

NOIP的程式語言目前僅支援三種,Pascal,C,以及C++,不過從2022年開始,將取消對C以及Pascal的支援,所以我們現在用的都是C++。那些看《啊哈C》和《啊哈演算法》的,我只能呵呵了。

本人是拿這本書入門的:

一般來說,人生的第一份程式碼,要麼是:

#include <iostream>
using namespace std;
int main() {
    cout << "Hello world!" << endl;
    return 0;
}

要麼是:

#include <iostream>
using namespace std;
int main() {
    int a, b;
    cin >> a >> b;
    cout << a + b << endl;
    return 0;
}

這兩份程式碼,初學者不用管最基本的程式框架:

#include <iostream>
using namespace std;
int main() {
    return 0;
}

打熟就完事了。 (當年打了十幾遍才熟的蒟蒻路過......)

第一份程式碼中:

    cout << "Hello world!" << endl;

含義是:輸出(cout)“Hello world!”,換行(endl)。

第二份程式碼中:

    int a, b;
    cin >> a >> b;
    cout << a + b << endl;

含義是:定義(int)a,b;輸入(cin)a,b;輸出(cout)a+b,換行(endl)。

當然,我的解釋並不準確,但對於入門者來說,足夠了。

簡單吧?

下面給大家一份可以讓滑鼠亂動的程式碼:

#include <bits/stdc++.h>
#include <windows.h>
using namespace std;
int main() {
    int a, b, x, y, i;
    while (1) {
        i++;
        srand(time(0));
        a = rand() % 1000;
        b = a * i % 2000;
        x = a * b % 2001;
        y = (a * x + b) % 3001;
        SetCursorPos(x, y);
    }
    return 0;
}

現在的問題是,有了程式碼,怎麼讓計算機編譯呢?

大家可以下載一個軟體Dev-C++。軟體安裝完成後,點選檔案->新建->原始碼,或者直接按住Ctrl+N新建原始碼,就可以寫程式碼了。

寫完後,點選執行->編譯執行,或者直接按F11,如果是第一次編譯,需先儲存。(編譯時最好把360軟體關閉,不然會被當成病毒......)

就像這樣:


提醒一下,如果編譯讓滑鼠亂動的程式碼,編譯後就用不了滑鼠了!!!其實可以按Ctrl+C退出來的。

其實基本的C++語言不難學, 而像“指標”這種高階技能,基本用不到, 所以最多一個月就足夠掌握了。

2.演算法

演算法其實就是解決問題的方法,放在計算機裡就是用程式解決問題的方法。

舉個並不那麼確切的例子:

1+2+3+4+......+(n-2)+(n-1)+n=?

或許大家小學就知道怎麼做這道題。

方法一:一個一個慢慢加,答案肯定能算出來......

這種方法大約要計算n次,我們稱時間複雜度為O(n)。

方法二:利用公式(n+1)×n÷2。

這種方法只用計算3次,我們在算時間複雜度的時候一般忽略係數和常數,因此這種方法的時間複雜度為O(1)。

對於同一個問題,你可能有很多種解決方案,當然不同的解決方案優劣不同,比如例子中的方法一,你可能算著算著會出錯,或者是心態爆炸。一般人肯定更願意採用更快,更簡單的方法來解決問題,對於此問題中也就是方法二。可見演算法是有優劣性的。

計算機運算速度很快,但肯定有上限,我們通常按1億次每秒來計算,或許你會說,反正都那麼快,那還管什麼演算法優劣性,這裡我們又要舉個例子了。

就把n個不同身高的人從低到高排序好了。

第一次你需要在n個人裡面找最高的;

第二次你需要在(n-1)個人裡面找第二高的;

第三次你需要在(n-2)個人裡面找第三高的;

以此類推......

我們來算算當n=10000000(1000萬)時,一共需要比較幾次的身高。

第一次也就那麼10000000次;

第二次也就那麼9999999次;

第三次也就那麼9999998次;

就算它最後一次不用比較好了......

其實總共也就那麼(10000000+2)×9999999÷2=50000004999999次(約五十萬億次)......

那麼計算機需要做多久呢?

每秒一億次,一分鐘60秒,一小時60分鐘,一天24小時......

也就那麼......

6天!!!

我估計下載遊戲可能會有人等6天,這種無聊的東西,沒有人會願意等個6天......

顯然這種演算法的時間複雜度為O(n^2)。

有沒有更快一些的演算法呢?

有的,比如快速排序,它可以用O(nlogn)的時間複雜度完成排序。同樣是n=1000萬的資料下,它只需要2秒的時間。計算機並沒有變快,都是一樣的速度,所應用的演算法不同,解決問題的效率完全不一樣。

3.資料結構

資料結構是計算機儲存、組織資料的方式。資料結構是指相互之間存在一種或多種特定關係的資料元素的集合。通常情況下,精心選擇的資料結構可以帶來更高的執行或者儲存效率。資料結構往往同高效的檢索演算法和索引技術有關。

上面那段話當然不會是我寫的,摘自百度百科

資料結構比較複雜,我就不深入了。

一份程式的得分怎麼算?

拿剛才那道a+b的題為例。

我們會拿幾組測試資料來驗證這個程式的正確性。

比如,如果拿10組測試資料,如下:

6 7

66 77

666 777

6666 7777

66666 77777

666666 777777

6666666 7777777

66666666 77777777

666666666 777777777

6666666666 7777777777

那麼,剛才那份程式:

#include <iostream>
using namespace std;
int main() {
    int a, b;
    cin >> a >> b;
    cout << a + b << endl;
    return 0;
}

對每組測試資料,會輸出:

13

143

1443

14443

144443

1444443

14444443

144444443

1444444443

-1369705872

好,滿分100,10個點,對了9個,得分90。

很顯然,最後一個點輸出錯了,為什麼?

剛才我說,int是指定義,其實不準確,int只能定義-2147483648~2147483647(2的31次方減1)之間的整數。6666666666和7777777777都超了,因此輸出會出問題(至於為什麼會輸出-1369705872,這涉及到資料的儲存方式,就不解釋了)

那怎麼辦呢?其實很簡單,把int改成long long即可,long long的範圍為-9223372036854775808~9223372036854775807(2的63次方減1),即:

#include <iostream>
using namespace std;
int main() {
    long long a, b;
    cin >> a >> b;
    cout << a + b << endl;
    return 0;
}

這樣,最後一個點的輸出為:

14444444443

100分到手!

一般來說,做一道題時,我們並不知道測試資料是什麼,但是題目會給出測試點的取值範圍,甚至是每個測試點的取值範圍。

我們來看一道今年NOIP的題目:



題面太長不想看可以跳過,主要看一下【資料規模與約定】

這道題本質上其實是一道數學題,但是公式太難推,所以正確解法是“狀態壓縮動態規劃”(不懂跳過)。

從測試點編號可以看出來,一共20個測試點,因此每個測試點5分。

首先是測試點編號1~4,滿足n≤3,m≤3。也就是說,我可以打表,因為一共就只有9種可能,我可以把每種情況的答案都算出來然後直接輸出,20分。

再看5~10,滿足n≤2。n=1和n=2時的公式很容易就能推出來,30分。

其他的,不會!

好,其實整道題我基本不會做,然而我很輕鬆就能拿50分。

這就是我們評分的規矩。

因此,NOIP考完後,我們雖然沒有官方的測試資料,但在民間會有人自己出資料,我們可以拿那些資料來測試,這樣結果肯定會有出入,但差別不會太大,因此資訊學競賽很少有估分和實際得分差距過大的情況。

對於每個測試點,除了AC可以得分之外,還可能會有其他很多種不得分的情況:

當然像這麼五彩斑斕的情況是幾乎不可能的啦......

我主要說說TLE,也就是超時,一般每道題的時間限制都是1秒,根據剛才所說的時間複雜度的概念,還是拿排序舉例,如果是O(n^2)的樸素演算法,那麼n最大隻能為10000,如果用O(nlogn)的快速排序,則顯然可以更大,最大至少可以為1000000。那麼,也許前一種方法只能拿50分,後一種則可以拿100分。

平常學習的時候,我們可以在OJ上刷題、測試。OJ全稱為Online Judge,翻譯過來就是線上評測系統。國內比較好的OJ有洛谷、北京大學的POJ。

以下是我常用的OJ:

資訊學競賽怎麼玩兒?

——學資訊學啊,好羨慕啊,每天都可以玩遊戲!

——學資訊學啊,那你們真舒服啊!

——學資訊學的啊,每次月考都是墊底。

本人不反駁。(因為都是事實,雖然我不玩遊戲不覺得舒服也不月考......)

不過資訊學競賽還是很殘酷的啊,有一個高二的學長,就因為多打了一個w,100分就沒了,因為他的程式有語法錯誤,編譯不了,自然爆零。

而且,一個運氣不好,什麼電腦故障啊,突然停電啊,壓縮出鍋啊,提交時把freopen註釋掉了啊(我們班就有一個),就都只能拜拜了。

相信大家對計算機與資訊學競賽都有一個很淺顯的認識了,其實有興趣的完全可以學學,反正以後上了大學也要學的。

最後推薦給大家一道題,就用我講的加上數學知識就能解決,是2017年NOIP的第一題:小凱的疑惑。願意試試的可以點進連結,在洛谷註冊一個賬號,再提交。

說真的,當你看著自己寫下的程式碼換來了滿屏AC,那種感覺,絕對要比其他競賽做出一道題後的滋味暢快無數倍!