Introduction to Competitive Programming
2/7
首先分析三種輸出輸入的優劣
C printf/scanf : 需要記%d %f %c %s 等等比較繁複的程式
C++ cin/cout : 較為簡單,不需處理資料類型,但比C標準輸入略慢一點
快讀/快寫 : 只能處理整數讀入/輸出,但是要比標準輸入輸出函數都快得多。
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
void write(int x){
if(x<0)
putchar('-'),x=-x;
if(x>9)
write(x/10);
putchar(x%10+'0');
return;
}
不需要另外多寫if-else述句,可以
for(int i=1;i<=5;i++){
cout<<i<<" \n"[i==5];
}
只需要無腦加在最前面就好,原理為放棄編譯速度/運行速度/代碼大小/指標指向 去增加其他東西
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#pragma G++ optimize(1)
#pragma G++ optimize(2)
#pragma G++ optimize(3)
#pragma G++ optimize("Ofast")
#pragma G++ optimize("inline")
#pragma G++ optimize("-fgcse")
#pragma G++ optimize("-fgcse-lm")
#pragma G++ optimize("-fipa-sra")
#pragma G++ optimize("-ftree-pre")
#pragma G++ optimize("-ftree-vrp")
#pragma G++ optimize("-fpeephole2")
#pragma G++ optimize("-ffast-math")
#pragma G++ optimize("-fsched-spec")
#pragma G++ optimize("unroll-loops")
#pragma G++ optimize("-falign-jumps")
#pragma G++ optimize("-falign-loops")
#pragma G++ optimize("-falign-labels")
#pragma G++ optimize("-fdevirtualize")
#pragma G++ optimize("-fcaller-saves")
#pragma G++ optimize("-fcrossjumping")
#pragma G++ optimize("-fthread-jumps")
#pragma G++ optimize("-funroll-loops")
#pragma G++ optimize("-fwhole-program")
#pragma G++ optimize("-freorder-blocks")
#pragma G++ optimize("-fschedule-insns")
#pragma G++ optimize("inline-functions")
#pragma G++ optimize("-ftree-tail-merge")
#pragma G++ optimize("-fschedule-insns2")
#pragma G++ optimize("-fstrict-aliasing")
#pragma G++ optimize("-fstrict-overflow")
#pragma G++ optimize("-falign-functions")
#pragma G++ optimize("-fcse-skip-blocks")
#pragma G++ optimize("-fcse-follow-jumps")
#pragma G++ optimize("-fsched-interblock")
#pragma G++ optimize("-fpartial-inlining")
#pragma G++ optimize("no-stack-protector")
#pragma G++ optimize("-freorder-functions")
#pragma G++ optimize("-findirect-inlining")
#pragma G++ optimize("-frerun-cse-after-loop")
#pragma G++ optimize("inline-small-functions")
#pragma G++ optimize("-finline-small-functions")
#pragma G++ optimize("-ftree-switch-conversion")
#pragma G++ optimize("-foptimize-sibling-calls")
#pragma G++ optimize("-fexpensive-optimizations")
#pragma G++ optimize("-funsafe-loop-optimizations")
#pragma G++ optimize("inline-functions-called-once")
#pragma G++ optimize("-fdelete-null-pointer-checks")
#pragma GCC optimize("Ofast")
#pragma GCC target("sse3","sse2","sse")
#pragma GCC target("avx","sse4","sse4.1","sse4.2","ssse3")
#pragma GCC target("f16c")
#pragma GCC optimize("inline","fast-math","unroll-loops","no-stack-protector")
#pragma GCC diagnostic error "-fwhole-program"
#pragma GCC diagnostic error "-fcse-skip-blocks"
#pragma GCC diagnostic error "-funsafe-loop-optimizations"
#pragma GCC diagnostic error "-std=c++14"
const 是固定變數,你用const宣告出來的東西在之後就不能再更改,同時這樣開在全域就也不需要再函式間傳遞也不用考慮初始化的問題。
const int N=1e5+5;
int arr[N]={};
時間複雜度是O(N),可以把整個arr的值都變成value。好處是方便快速,但可賦值有限制 ex: -1 , 0 , 0x3f3f3f3f
同時要使用則要記得引入標頭檔cstring
如果要使用random等函式,不推薦在比賽中使用時間種子碼以及rand,而是推薦使用這個mt19937,產生高性能隨機數,且更為隨機以及可測試一點。
#include<iostream>
#include<random>
using namespace std;
signed main() {
mt19937 mt(hash<string>(":poop:"));
for(int i=1;i<=5;i++) cout<<mt()<<" \n"[i==5];
return 0;
}
可以直接讓 \(i\) 遍歷 \(arr\) 裡面的東西,且auto也不用考慮型態,只需要無腦這樣寫,對於實作的方便性很高,直接讓編譯器宣告他的變數型態。
在你發現你的代碼需要把很多東西改型態的時候,只需要define這行就可以一次性把所有int變成long long,當然還有其他東西也可以這樣用。不過需要記得把main函數的定義改掉,改成int32_t或是signed都可以
ex :
#define int double
#define x first
#define y second
#define rep(a,b) for(int a=1;a<=b;a++)
與int,long long 沒什麼差別,特點有是超級大的定義域(可以儲存1e36內的數字),但C/C++標準IO是不認識__int128這種數據類型,cin和cout是無法輸出__int128的,所以我們要自己實現輸入輸出,其他的運算,與int沒啥差別。
__int128能直接做加减乘除赋值
c.begin() 返回一個迭代器,它指向容器c的第一個元素
c.end() 返回一個迭代器,它指向容器c的最後一個元素的下一個位置
c.rbegin() 返回一個逆序迭代器,它指向容器c的最後一個元素(比begin慢)
c.rend() 返回一個逆序迭代器,它指向容器c的第一個元素前面的位置(比end慢)
使用這些訪問方式,就可以不用去重新定義它的大小或是重新確認一次,同時也可以訪問那些沒辦法隨機存取的容器類別,可以直接叫出來。
prev是指用來獲取一個距離指定迭代器左側 \(n\) 個元素的迭代器。
next是指用來獲取一個距離指定迭代器右側 \(n\) 個元素的迭代器。
假設現在有一個\(vector[10]={0,1,2,3,4,5} ; iter=2 ;\)
則會有以下四種
\(prev(iter,2), iter=0\)
\(prev(iter,-2), iter=4\)
\(next(iter,2), iter=4\)
\(next(iter,2), iter=0\)
就可以無需使用其他寫法去進行移動迭代器
就像是看到的那樣。
這樣的寫法可以讓你再換題的時候直接刪除solve函式,不須刪除其他東西,可以有效提高寫題目的效率。
先比解題數,再比penalty
那 penalty 怎麼算?
每題解出來的時間加起來,再加上多錯的次數*20
以此記分板來看 penalty 為 35 + 7 + 2*20 = 82
一個人先打好 default code
通常為手速最快或者主要coder
#include<bits/stdc++.h> #define ll long long using namespace std; int main(){ ios::sync_with_stdio(0); cin.tie(0); return 0; }
其他人開始讀題目
通常為英文閱讀好、題目辨識難度強的人
殺首殺
每題要開始寫直接複製default code就好,減少寫程式碼量
比賽中記分板,記得一定要定期刷新記分板,看大家解什麼題目
每題的氣球顏色都是固定的,因為記分板只能從電腦看,但如果隊友在上機看不了記分板就要觀察其他隊的氣球顏色
比賽中隨時可以印出紙本code
那什麼時候要印
如果你是主coder 遇到bug
可以考慮給隊友找 自己再去開新的題目
如果一次很多題目會寫,分析一下每題需要時間,根據greedy,先挑時間短的寫
比賽只有五小時,很短所以也要隨時注意時間
剩下一小時只留2~3題,最後半小時只留1~2題
比賽有五小時,很長XD
所以要定期去吃點心(建議1~2小時補充一次)
而且NCPC點心很好吃,很快就被搶光要搶要快(X
養成良好分配習慣,知道彼此工作
賽後檢討彼此疏失或策略
修正策略
並且補題,也是最重要的,不補題跟沒打比賽一樣
不會解的題目還是不會
多打比賽,增加實作經驗
多寫題單,可以跟隊友溝通各自去練什麼演算法
多看電神的code,可以看到各種有趣精湛的寫法
各自專精擅長或有興趣的比賽也才能發揮各自所長
https://zhuanlan.zhihu.com/p/47541491
https://hackmd.io/@billsun/BJByCIUHf?type=view
cd .. 回到上一層資料夾 cd ../.. 回到上上層資料夾 cd test 到當前目錄的test資料夾 ls 顯示當前資料夾的檔案 cat a.cpp 印出當前檔案的內容 mkdir test 在當前目錄建立 test 的資料夾 rm a.cpp 刪除 a.cpp 的檔案
g++ solve.cpp -o main -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow
g++ solve.cpp 編譯solve.cpp的檔案成 a.out 檔 g++ solve.cpp -o ac.out 編譯solve.cpp的檔案成 ac.out 檔 g++ solve.cpp -std=c++14 編譯solve.cpp的檔案成 a.out 檔 並且編譯版本為 c++14 ./a.out 執行 a.out 檔
-fsanitize=undefined 插入各種undefined behavior檢查,會在執行期輸出錯誤訊息 -Wall -Wextra 把warning都開起來,常能預防bug發生 -Wshadow 當有宣告了相同變數名稱的情形發生時予以警告
g++ solve.cpp -o main -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow
難道每次都要打那麼長嗎 ?
可以使用 alias 把原本要打的那串 define 掉
alias [name]='[value]'
等同於 c++ 內的 #define
#define ll long long
alias g++ = `g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow`
使用 Alias 之後
g++ solve.cpp
等價於
g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow solve.cpp
factor 100
競賽中或者平常寫題目時,如果遇到 bug 不能及時找到
有以下方法
gen.py/gen.cpp
先將兩份程式碼分別編譯成 ac, wa
不斷用 gen.py
產生測資放到 input 檔案裡面
再把 input 檔案分別為給 ac, wa 兩份執行檔
用 for 迴圈一直跑到 output 內容不同為止
set -e 可以偵測程式 RE 時會停下來
set -e g++ ac.cpp -o ac g++ wa.cpp -o wa for ((i=0;;i++)) do echo "$i" python3 gen.py > input ./ac < input > ac.out ./wa < input > wa.out diff ac.out wa.out || break done
跑到停下來後有兩種可能
因此當比賽時,如果對於錯誤毫無頭緒,
不應該只是盯著程式碼看,而是開始寫對拍才是最有效率的做法
而平時練習、作業找不到bug的情況也應該要先對拍
真的找不到問題 對拍也對不到 再去問其他電神錯在哪
gen.py/gen.cpp
聽起來好麻煩,為了 debug 還要多寫一份 code
而且不一定寫得出來還有可能錯,怎麼辦?
如果連暴力的程式碼都寫不出來 那你應該多練練你的實作能力…
如果不知道怎麼寫請參考早上 brute force 講義
善用 next_permutation 以及好好熟悉遞迴
沒有暴力寫不了的程式碼 只有你不練不夠實作能力
gen.py/gen.cpp
from random import * n = randint(1, 100) # 隨機產生 1~100的整數 ch = chr(ord('a') + randint(0, 25)) # 隨機產生 'a'~'z' 其中一個字元 choiceSet = sample(s, 4) # 從集合 s 選出 4 個不同的元素 choiceSet = sample(range(1, n+1), 4) # 從整數 1~n 選出 4 個不同的元素 shuffle(arr) # 把序列 arr 順序打亂
from random import * n = randint(3, 6) arr = [i+1 for i in range(n)] shuffle(arr) print(n) print(*arr) #輸出 arr 的內容並且用空格隔開
s = "" n = randint(1, 10) for i in range(n): s += chr(ord('a') + randint(0, 25)) print(s)
每次從比自己小的節點選一個當連接自己的邊
from random import * n = randint(3, 6) print(n) for i in range(2, n+1): print(randint(1, i-1), i)
無自環 可以判斷兩個點不相同
無重邊 可以用 dict/map 紀錄哪些邊用過了
連通圖 = 樹 + 一些額外的邊
from random import * n = randint(5, 10) m = randint(n-1, n+3) print(n, m) edge = list() #construct tree for i in range(2, n+1): x = randint(1, i-1) y = i edge.append([min(x, y), max(x, y)]) print(x, y) #add extra edge for i in range(m-(n-1)): x = randint(1, n) y = randint(1, n) while x == y or [min(x, y), max(x, y)] in edge: x = randint(1, n) y = randint(1, n) print(x, y) edge.append([min(x, y), max(x, y)])
Don't use rand(): a guide to random number generators in C++
mt19937 是 c++ 的一種隨機產生器
mt19937 gen(chrono::steady_clock::now().time_since_epoch().count()); int randint(int lb, int ub) { return uniform_int_distribution<int>(lb, ub)(gen); }
mt19937 gen(chrono::steady_clock::now().time_since_epoch().count()); int randint(int lb, int ub) { return uniform_int_distribution<int>(lb, ub)(gen); } int main(){ int n = randint(1, 10); //產生 0~10 之間的任意整數 char ch = 'a'+randint(0, 25); //產生 a-z 之間的任意字母 vector<int> ord(n); iota(ord.begin(), ord.end(), 1); shuffle(ord.begin(), ord.end(), gen); cout << n << endl; for(int i : ord) cout << i << " "; cout << endl; }
set -e g++ ac.cpp -o ac g++ wa.cpp -o wa for ((i=0;;i++)) do echo "$i" python3 gen.py > input ./ac < input > ac.out ./wa < input > wa.out diff ac.out wa.out || break done
可能有問題的那段程式碼 把變數 print 出來
cout << "Before" << endl; for(int i = 0; i < n; i++){ cout << arr[i] << ' '; } cout << endl; . . . cout << "After" << endl; for(int i = 0; i < n; i++){ cout << arr[i] << ' '; } cout << endl;
1. debug 完 bug 忘記註解/刪掉
2. debug 完把 printer 註解,但沒有註解乾淨
3. 好麻煩喔 先把所有 cout 都註解la 再把要輸出的反註解
4. 喔 好像多註解到要輸出的答案了
penalty += 80(20 * 4)
debug 時,很常會用cout輸出每個變數的值
在這邊建議輸出debug的值時使用cerr
他輸出的東西只會出現在 Standard error stream
不會輸出在 Standard output stream
答案比對是比對 Standard output stream 的內容
因此就算忘記刪也不會造成影響
// __LINE__ 會輸出呼叫的位置在程式碼第幾行 cerr << "This is debug message on " << __LINE__ << " line : " << x << endl;
如果沒有刪掉可能造成 TLE
for(int i = 0; i < n; i++){
x *= 2;
arr[x] += v;
for(int j = 0; j < n; j++){
cerr << arr[j] << " ";
}
cerr << endl;
}
原本可能 \(O(N)\) 的程式碼變成 \(O(N^2)\)
或者常數太大 (輸出數量變成原本的兩倍)
讓你更好 debug 的工具
#ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif
#ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif
#ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif
#ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif
如果有 define LOCAL
那就會…
#ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args))
否則就會…
#else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif
在本機 define LOCAL
g++ solve.cpp -D LOCAL
則編譯時就會 define LOCAL
但每次都要打 -D LOCAL 很麻煩…
還記得 alias
alias g++ = `g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow`
改成
alias g++ = `g++ -std=c++14 -fsanitize=undefined -Wall -Wextra -Wshadow -D LOCAL`
:happymention:
debug(x)
string x = "abc";
debug(x);
int y = 123;
double z = 1.23;
debug(y, z);
orange(begin, end)
int arr[5]={2,1,4,3,5}; orange(arr, arr+5); vector<string> strVec = {"Alice", "Bob"}; orange(strVec.begin(), strVec.end());
```cpp= #ifdef LOCAL // =========== Local =========== void dbg() { cerr << '\n'; } template<class T, class ...U> void dbg(T a, U ...b) { cerr << a << ' ', dbg(b...); } template<class T> void org(T l, T r) { while (l != r) cerr << *l++ << ' '; cerr << '\n'; } #define debug(args...) (dbg("#> (" + string(#args) + ") = (", args, ")")) #define orange(args...) (cerr << "#> [" + string(#args) + ") = ", org(args)) #else // ======== OnlineJudge ======== #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #define debug(...) ((void)0) #define orange(...) ((void)0) #endif
他用來判斷一個條件是否成立,
如果條件成立則不會發生任何事
如果條件不成立,則會造成程式RE(Runtime Error)
通常用於 debug 不確定會不會錯
或者想 submit 時 不確定有沒有問題用的
assert(x <= 5);
long long fpow(int x, int y){ long long ret = 1; for(;y;y>>=1, x*=x){ if(y&1) ret = ret * x; } return ret; } int main(){ cin >> n; auto [x, y] = solve(n); assert(fpow(x, y) * y + fpow(y, x) * x == n); cout << x << " " << y << endl; }
long long fpow(int x, int y){ long long ret = 1; for(;y;y>>=1, x*=x){ if(y&1) ret = ret * x; } return ret; } int main(){ cin >> n; auto [x, y] = solve(n); assert(fpow(x, y) * y + fpow(y, x) * x == n); cout << x << " " << y << endl; }
long long fpow(int x, int y){ long long ret = 1; for(;y;y>>=1, x*=x){ if(y&1) ret = ret * x; } return ret; } int main(){ cin >> n; auto [x, y] = solve(n); assert(fpow(x, y) * y + fpow(y, x) * x == n); cout << x << " " << y << endl; }
long long fpow(int x, int y){ long long ret = 1; for(;y;y>>=1, x*=x){ if(y&1) ret = ret * x; } return ret; } int main(){ cin >> n; auto [x, y] = solve(n); assert(fpow(x, y) * y + fpow(y, x) * x == n); cout << x << " " << y << endl; }
long long fpow(int x, int y){ long long ret = 1; for(;y;y>>=1, x*=x){ if(y&1) ret = ret * x; } return ret; } int main(){ cin >> n; auto [x, y] = solve(n); assert(fpow(x, y) * y + fpow(y, x) * x == n); cout << x << " " << y << endl; }
或者想猜測資,判斷是否大於某個值,就可以assert
再加上 TLE 一起用
每次可以把測資分三半去猜測資 複雜度 \(O(log_3N)\)
cin >> n; if(n >= 10){ // RE assert(0); } else if(n > 5){ // TLE while(1){} } else{ // WA return 0; }