9/15 資訊校隊培訓
建國中學 賴昭勳
校內複賽 | 10/5 | 選校隊 11(?)+3人 |
北市賽 | 11/15前 | 前二等獎進全國 (11人) |
全國賽 | 12月 | 前二等進選訓 (10人) |
TOI 入營考 | 3月初 | 選20個進一階 (+女保) |
一階 | 約 3/15~3/28 | 模考前12名進二階 |
二階 | 4月 | 選4名國手 |
IOI 2022 | 7月 (?) 在印尼 | 台灣加油w |
YTP | 10/9 | 好吃 前十多組晉級專題階段(賺爛了賺爛了) |
ISSC | 10/16 | 一校最多兩隊* 台中比賽,題目品質極不穩但有錢拿 |
HP Codewars | 11月 | 有一堆獎品可以拿 線上賽的話就很無聊QQ |
NPSC | 12月 | 初賽取25隊/一校最多三隊,好題,賺 |
除了拚升學上資工之外...
當然,如果你覺得自己不適合的話也沒有關係!
2021 建中校培講義(?)
另外,可以參加每週二晚上的資訊讀書會!
使用 Online Judge
除了台灣的實體比賽外,還有許多線上競賽可以打
Codeforces 通常在 22:35~00:35
Atcoder 周末 20:00~22:00
Google Codejam - 年度
Facebook Hacker Cup - 年度,深夜場,怪賽制
例: 給定兩個正整數 \(a, b\),輸出\(a, b\)的最大公因數
一個好的演算法應該能夠對於任意範圍內的\(a, b\)輸出正確答案。
問題和答案通常以純文字形式輸入,或是用數字,字串等格式溝通。
Online Judge 判斷回答正確的方法為,使用一些測試資料 (測資) 來檢測程式的輸出是否符合條件。
正確解答必須在規定的時間限制 (TL) 和記憶體限制 (ML) 執行出正確答案。
估算執行時間的方法
const int maxn = 100005;
int a[maxn];
for (int i = 0;i < n;i++) cin >> a[i];
int ans = 0;
for (int i = 0;i < n;i++) {
for (int j = 0;j < n;j++) {
ans = max(ans, a[i] - a[j]);
}
}
cout << ans << endl;
const int maxn = 100005;
int a[maxn];
for (int i = 0;i < n;i++) cin >> a[i];
int ma = 0, mi = 0;
for (int i = 0;i < n;i++) {
ma = max(ma, a[i]);
mi = min(mi, a[i]);
}
cout << ma - mi << endl;
問題:
給定一個整數序列 \(a\)
求出相差最大的兩項之差
\(max_{1 \leq i, j \leq n} |a_i - a_j|\)
Why?
n (序列長度) | 程式 1 (兩層 for) | 程式 2 (一層 for) |
---|---|---|
3 | 9 | 6 |
1000 | 1000000 | 2000 |
100000 | 10000000000 | 200000 |
當\(n\)的值很大的時候,第二個程式的時間跟\(n\)差不多,第一個程式卻慢了很多...
第一個程式執行 \(n^2\)次操作,第二個程式執行了\(2n\)次操作。
我們分別用 \(O(n^2)\)和 \(O(n)\)來表示兩個程式的時間複雜度
定義一個問題在大小\(n\)時的執行次數為\(f(n)\)
一個演算法的時間複雜度為 \(f(n) \in O(g(n))\)
代表存在兩個正整數 \(M, n_0\)使得
\(\forall n \geq n_0, f(n) \leq Mg(n)\)
通常我們在乎的是符合上述條件中,成長速度最慢的函數\(g(n)\)
空間複雜度也是一樣的道理
考慮的是一個問題使用的記憶體空間
成長速度: \(指數 > 多項式 > 底數 > 常數\)
忽略\(g(n)\)的常數項: \(O(5x^2) \rightarrow O(x^2)\)
多個函數相加時,取成長速度最快的一項:
\(O(g_1) + O(g_2) = O(max(g_1, g_2))\)
\(O(g_1) * O(g_2) = O(g_1g_2)\)
int n;
cin >> n;
bool isprime = 1;
for (int i = 2;i*i <= n;i++) {
if (n % i == 0) isprime = 0;
}
cout << isprime << endl;
int n;
cin >> n;
int a[n], pref[n];
for (int i = 0;i < n;i++) {
cin >> a[i];
pref[i] = a[i];
if (i > 0) pref[i] += pref[i-1];
}
int ans = 0;
for (int i = 0;i < n;i++) {
for (int j = 0;j < i;j++) {
ans = max(ans, pref[i] - pref[j]);
}
}
cout << ans << endl;
int n;
cin >> n;
int cnt = 0;
while (n > 0) {
cnt++;
n /= 2;
}
cout << cnt << endl;
int n, k;
cin >> n >> k;
int a[n], pref[n];
for (int i = 0;i < n;i++) {
cin >> a[i];
pref[i] = a[i];
if (i > 0) pref[i] += pref[i-1];
}
int ans = 0, ind = 0;
for (int i = 0;i < n;i++) {
while (ind <= i && pref[i] - pref[ind] + a[ind] > k) {
ind++;
}
ans += i - ind + 1;
}
cout << ans << endl;
我這個寫法到底夠不夠快?
\(n\)的大小
\(10\)
\(20\)~\(24\)
\(100\)~\(500\)
\(1000\)~\(5000\)
\(10000\)~\(10^5\)
\(10^5\)~\(10^6\)
\(\geq 10^6\)
對應的複雜度
\(O(n!)\)
\(O(2^n), O(n*2^n)\)
\(O(n^3logn), O(n^3)\)
\(O(n^2logn), O(n^2)\)
\(O(n\sqrt n)\)
\(O(nlogn)\)
\(O(n)\)
電腦一秒可以跑\(3*10^8\)個基本運算 (+, -, *, =)等。
左邊僅為參考用w
*假設時限 1 秒
資訊社員: 怎麼有點似曾相識...
-> 詢問的數字
回答為 \(\geq\)
回答為 \(<\)
注意到兩條黑色的線段不相交!
如果我們詢問的數字在答案區間的中間的話
那就可以把答案範圍平分成兩塊!
當答案區間只有一個數字時,我們就知道答案了,因此問題相當於最多要切多少次才能得到長度 1 的區間。
大約是 \(O(\log n)\)次,其中\(n\)是一開始的區間長度。
給你 \(n\) 個整數的序列\(a\),請輸出第\(k\)小的數字
(不用 sort)
\(1 \leq k \leq n \leq 10^6, 1 \leq a_i \leq 10^3\)
跟猜數字一樣,先猜一個答案\(x\),而我們想知道這個答案是\(\geq\)或\( < \)實際答案。
我們可以遍歷整個序列,數有幾個數字\(\leq x\)
假設有\(\geq k\)個數字,那\(x\)就大於等於實際答案,否則\(x\)就小於實際答案,藉此縮小範圍。
複雜度\(O(n\log C)\),其中\(C\)是答案範圍
剛剛的兩個題目能這樣做的原因,是一個叫單調性的性質。
前面問題可以看成:
在一個範圍內,尋找最小符合條件的數字
而單調性指的是: 存在一個數字\(x\)
使所有小於\(x\)的數字都不符合條件,
不小於\(x\)的數字都符合條件。
int low = 0, up = 10001;
while (low < up - 1) {
int mid = (low + up) / 2;
if (condition(mid)) {
up = mid;
} else {
low = mid;
}
}
cout << up << "\n";
左開右開式: \(low\)必為 \(0\),\(up\)必為\(1\)
其中 \(f\) 稱為函數/函式
\(x\) 是輸入值
\(y\) 是輸出值
ex. \(f(x) = 71x + 22, f(a, b) = 3a^3 - b^2\)
在C++, 函式除了可以回傳值,還可以在裡面改變其他變數,把一段程式碼包成一個「動作」
上述函式輸入一個非負整數\(x\),輸出\(2^x\)
把左邊的每個環移到中間,一次只能搬一個環,而且上面的環一定要比下面小。
有一個\(8*8\)的棋盤,問有多少個方式放置八個皇后,使得任兩個皇后不互相攻擊。
一個皇后可以向她上下左右,以及斜向共八個方向任意距離直線攻擊。
電腦裡面的數字都是用二進位表示,因此也有跟二進位相關的運算。
AND | & |
OR | | |
NOT | ~ |
XOR | ^ |
左移 | << |
右移 | >> |
「我絕不使用暴力解決問題」- 開學宣誓詞
可以使用遞迴減少實作難度!
另一種單調性!
考慮這個問題: 給定一個正整數序列與整數\(k\),輸出有多少的序列的子區間內數字總和不超過\(k\)。
\(n \leq 10^6\)
一個子區間由他的左界和右界決定。
假設我們枚舉右界\(r\),對每個右界考慮最左邊的\(f(r) = l\)使得\([l, r]\)總和小於\(k\),
那麼就有\(r - l + 1\)的右界為\(r\)的區間符合條件。
那可以發現,隨著右界\(r\)變\(r+1\),對應的左界\(f(r)\)也會非嚴格遞減。因此我們要檢查的時候就可以直接從\(f(r)\)開始判斷!
int a[maxn];
for (int i = 0;i < n;i++) cin >> a[i];
int sum = 0, lef = 0;
ll ans = 0;
for (int i = 0;i < n;i++) {
sum += a[i];
while (lef <= i && sum > k) {
sum -= a[lef];
lef++;
}
ans += i - lef + 1;
}
cout << ans;