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
求出相差最大的兩項之差
max1≤i,j≤n∣ai−aj∣
Why?
n (序列長度) | 程式 1 (兩層 for) | 程式 2 (一層 for) |
---|---|---|
3 | 9 | 6 |
1000 | 1000000 | 2000 |
100000 | 10000000000 | 200000 |
當n的值很大的時候,第二個程式的時間跟n差不多,第一個程式卻慢了很多...
第一個程式執行 n2次操作,第二個程式執行了2n次操作。
我們分別用 O(n2)和 O(n)來表示兩個程式的時間複雜度
定義一個問題在大小n時的執行次數為f(n)
一個演算法的時間複雜度為 f(n)∈O(g(n))
代表存在兩個正整數 M,n0使得
∀n≥n0,f(n)≤Mg(n)
通常我們在乎的是符合上述條件中,成長速度最慢的函數g(n)
空間複雜度也是一樣的道理
考慮的是一個問題使用的記憶體空間
成長速度: 指數>多項式>底數>常數
忽略g(n)的常數項: O(5x2)→O(x2)
多個函數相加時,取成長速度最快的一項:
O(g1)+O(g2)=O(max(g1,g2))
O(g1)∗O(g2)=O(g1g2)
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~105
105~106
≥106
對應的複雜度
O(n!)
O(2n),O(n∗2n)
O(n3logn),O(n3)
O(n2logn),O(n2)
O(nn)
O(nlogn)
O(n)
電腦一秒可以跑3∗108個基本運算 (+, -, *, =)等。
左邊僅為參考用w
*假設時限 1 秒
資訊社員: 怎麼有點似曾相識...
-> 詢問的數字
回答為 ≥
回答為 <
注意到兩條黑色的線段不相交!
如果我們詢問的數字在答案區間的中間的話
那就可以把答案範圍平分成兩塊!
當答案區間只有一個數字時,我們就知道答案了,因此問題相當於最多要切多少次才能得到長度 1 的區間。
大約是 O(logn)次,其中n是一開始的區間長度。
給你 n 個整數的序列a,請輸出第k小的數字
(不用 sort)
1≤k≤n≤106,1≤ai ≤103
跟猜數字一樣,先猜一個答案x,而我們想知道這個答案是≥或<實際答案。
我們可以遍歷整個序列,數有幾個數字≤x
假設有≥k個數字,那x就大於等於實際答案,否則x就小於實際答案,藉此縮小範圍。
複雜度O(nlogC),其中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)=3a3−b2
在C++, 函式除了可以回傳值,還可以在裡面改變其他變數,把一段程式碼包成一個「動作」
上述函式輸入一個非負整數x,輸出2x
把左邊的每個環移到中間,一次只能搬一個環,而且上面的環一定要比下面小。
有一個8∗8的棋盤,問有多少個方式放置八個皇后,使得任兩個皇后不互相攻擊。
一個皇后可以向她上下左右,以及斜向共八個方向任意距離直線攻擊。
電腦裡面的數字都是用二進位表示,因此也有跟二進位相關的運算。
AND | & |
OR | | |
NOT | ~ |
XOR | ^ |
左移 | << |
右移 | >> |
「我絕不使用暴力解決問題」- 開學宣誓詞
可以使用遞迴減少實作難度!
另一種單調性!
考慮這個問題: 給定一個正整數序列與整數k,輸出有多少的序列的子區間內數字總和不超過k。
n≤106
一個子區間由他的左界和右界決定。
假設我們枚舉右界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;