ACM-ICPC 2017 泰國第 7
TOI 2!
略玩CTF
熟悉程式語言:
C++ ⭐⭐⭐(從國中寫到現在)
PHP ⭐⭐⭐(寫玩具用)
Python3 ⭐(學好玩的?)
演算法是什麼?
[al-guh-rith-uh m]
不同型式的資訊競賽
Text
Text
Text
Text
C++ | 最常使用的解題語言 |
---|---|
C | 通常從大學才學程式設計的人使用 |
Java | 解特殊題型 (大數運算) |
Python 3 | 解特殊/簡單題型 (大數運算) |
- NTHU ACM 比賽團隊
使用者名稱@電腦名稱:目錄$
使用`pwd`指令可以看目前的目錄名稱
使用`ls`指令可以看目錄裡有什麼檔案
桌面的資料在 Desktop
使用`cd 目錄`指令可以進入目錄
其他常見的編譯器有:Clang (Mac, 部分linux使用), MSVC
指令:`g++ C++檔案 -o 程式檔`
如果沒有設定 `-o 程式檔` 預設會是a.out
指令:`./程式檔`
注意要有 ./
除了單純的使用之外,通常會附加一些參數來增加功能
-std=c++XX
C++的版本是最需要注意的選項,設定錯誤可能會導致在不同電腦上編譯錯誤
-Wall -Wextra
讓編譯器顯示更多的錯誤
開起來經常得可以找出意想不到的BUG
int a = -1;
unsigned b = 0;
if( a < b )
cout<<"AC!";
else
cout<<"You dead!";
g++ a.cpp
./a.out
You dead!
輸出
int a = -1;
unsigned b = 0;
if( a < b )
cout<<"AC!";
else
cout<<"You dead!";
g++ -Wall -Wextra a.cpp
輸出
-OX
g++ 可以盡量的在規範內加速你的程式
一般比賽與應用上,都會使用O2優化
開啟優化後,可能會讓未定義行為造成的問題更明顯,讓程式更容易debug
開啟一個文字檔案,在開頭打上`#!/bin/sh`存檔
這個檔案就是一個Script
存檔之後,要設定這個檔案是可以執行的
#!/bin/sh
rm a.out # 刪除舊的程式檔案
g++ -std=c++11 -O2 -Wall -Wextra -O2 $1
./Script名稱 CPP檔案名
程式題目哪裡最重要?
A. 問題描述
B. 輸入與輸出格式
D. 資料大小
C. 範例測資
矩陣是將一群元素整齊的排列成一個矩形...
第一行有三個正整數 R, C, M ...
3 2 3
0<R, C, M <10
- 某出題者的評論
第一題
分數(0~100 間)
人數為 1~20 的整數
第二題
三個介於 1~10 之間的正整數
第三題
\(N < 10000\) ,\( 0 \leq L , R < 10000000 \)
\(2 \leq N \leq 100000\)
第四題
如果\(N=100\),使用泡沫排序法,程式在1秒內跑得完嗎?
如果\(N=10000\),使用泡沫排序法,程式在1秒內跑得完嗎?
如果\(N=10000\),使用快速排序法,程式在1秒內跑得完嗎?
如果\(N=10000000\),使用快速排序法,程式在1秒內跑得完嗎?
時間複雜度
int mx = a[0];
for(int i=1;i<N;++i)
{
mx = max( mx , a[i] );
}
如果\(f(x)\in \mathcal{O}(g(x))\)
就存在\(c\)與\(x_0\)
如果\(x>x_0\)
\(c\times g(x)\geq f(x)\)
如果\(f(x)\in \mathcal{O}(g(x))\)
如果\(f(x)\in \mathcal{O}(g(x))\)
就存在\(c\)與\(x_0\)
\(x_0\)
\(c\times\)
如果\(f(x)\in \mathcal{O}(g(x))\)
就存在\(c\)與\(x_0\)
如果\(x>x_0\)
\(x_0\)
\(c\times\)
\(x\)
如果\(f(x)\in \mathcal{O}(g(x))\)
就存在\(c\)與\(x_0\)
如果\(x>x_0\)
\(c\times g(x)\geq f(x)\)
\(x_0\)
\(c\times\)
\(x\)
\(c\times g(x)\)
\(f(x)\)
如果\(x\geq1\)
\(x^2-x+2\)
\(\leq x^2+x+2\)
\(\leq x^2+x^2+2x^2=4x^2\)
如果\(x\geq1\)
\(x^2-x+2\)
\(\leq x^2+x+2\)
\(\leq x^2+x^2+2x^2=4x^2\)
設\(c=4,x_0=1\)
如果\(x_0\leq x\)
\(x^2-x+2\leq cx^2\)
因此
\(x^2-x+2 \in O(x^2)\)
\(f(x)\in \mathcal{O}(x^n)\)
// Find max value index in array
int index = -1;
for(int i=0;i<N;++i)
{
bool flag = true;
for(int j=0;j<N;++j)
{
if( a[j] > a[i] )
flag = false;
}
if( flag )
index = i;
}
如果一個演算法複雜度是\(O(x^2)\),表示該方法最糟不會比所有\(O(x^2)\)的方法糟糕
// Find max value index in array
int index = 0;
for(int i=1;i<N;++i)
{
if( a[i] > a[index] )
index = i;
}
\(O(x^5)\)
\(O(x^3)\)
\(O(x)\)
\(O(\log n!)=O(n\log n)\)
把題目測資上限\((N=20000)\)代入
如果大於\(10^8\),基本上就會TLE
scanf/printf
cin/cout
不論喜歡用何種方法,至少要熟悉一套
cin
getline
鍵盤輸入
檔案輸入
串流輸入
C++中只要知道串流怎麼操作
就能處理任意能接上串流的資料
標準輸入STDIN
標準輸出STDOUT
cout
鍵盤輸出
檔案輸出
串流輸出
int a;
double b;
cin>>a>>b;
./a.exe < input.txt
int a = 1234;
cout >> a >> endl;
./a.out > answer.txt
./a.out < test.in > res.out # 可以一起用
int a;
scanf("%d", &a);
printf("%d", a);
C語言無法自動判斷型態,要透過%d等標記來協助
float | double | long double | |
---|---|---|---|
scanf | f | lf | Lf |
printf | f | f | Lf |
printf(C++11) | f | lf, f | Lf |
scanf/printf語言的標記在不同標準上會有差異
Ctrl+Z
int a;
while( cin>>a )
{
//輸入成功就重複執行
}
int a;
while( scanf("%d", &a) != EOF )
{
//輸入成功就重複執行
}
C語言的字串為使用 \0 結尾的字元陣列
char str[100] = "C Style String";
C++語言的字串為string物件
string str = "C++ string";
C++的字串可以自動控制長度,可以減少陣列長度不夠的問題
C語言的字串若善加使用,效率比C++還要好
C | C++ | |
---|---|---|
C字串 | fgets | cin.getline |
C++字串 | getline |
char buf[100];
cin.getline(buf, 100);
這程式可以讀幾個字元?
陣列開大一點,不會有問題;陣列開小一點,絕對會出事
int a;
char buf[10];
cin >> a;
cin.getline(buf, 10);
cout << a << "," << buf << "<";
123
abc
輸入
輸出
123,<
int a;
cin >> a;
1 | 2 | 3 | \n | a | b | c |
---|
cin 開始讀取整數給a
int a;
cin >> a;
2 | 3 | \n | a | b | c |
---|
cin 開始讀取整數給a
1 |
---|
int a;
cin >> a;
3 | \n | a | b | c |
---|
cin 開始讀取整數給a
2 |
---|
int a;
cin >> a;
\n | a | b | c |
---|
cin 開始讀取整數給a
3 |
---|
int a;
cin >> a;
\n | a | b | c |
---|
cin 發現不是數字,停止讀取
\n |
---|
getline(buf, 10);
\n | a | b | c |
---|
cin.getline開始讀取
getline(buf, 10);
a | b | c |
---|
cin.getline讀取到換行,直接結束
\n |
---|
int a;
char buf[10];
cin >> a;
cin.get(); // 讀一個字元
cin.getline(buf, 10);
cout << a << "," << buf << "<";
123
abc
輸入
輸出
123,abc<
int a;
char buf[10];
cin >> a;
cin.getline(buf, 10);
cout << a << "," << buf << "<";
123 abc
輸入
輸出
123, abc<
string str;
getline(cin, str);
cout << str ;
參數的設計有點詭異
char buf[10];
fgets(buf, 10, stdin);
cout << buf << "X";
abcd1234\n
輸入
輸出
abcd1234
X
char buf[10];
fgets(buf, 10, stdin);
// 如果沒有換行符號會RE!
*strchr(buf, '\n') = 0;
cout << buf << "X";
abcd1234\n
輸入
輸出
abcd1234X
strchr : 找到字串中某個字元的位置
在一行裡面有很多個數字,你能把數字都加起來嗎?
123 456 789
1 2 3 4 5 6 7 8 9 10
11
1368
55
11
範例輸入
範例輸出
不要在輸入函數上做太多動作
string buf;
while( getline(cin, buf) )
{
}
"123 456 789"
#include<sstream>
std::stringstream ss;
#include<sstream>
std::stringstream ss;
ss << "1234" << ' ' << 5678;
//ss現在為 "1234 5678"
ss << "1234" << ' ' << 5678;
//ss現在為 "1234 5678"
int a;
ss >> a; // a = 1234, ss現在為 " 5678"
ss >> a; // a = 5678, ss現在為 ""
string buf;
stringstream ss;
while( getline(cin, buf) )
{
ss.clear(); // 記得初始化
ss.str(buf); // 直接設定串流裡面的資料
int tmp, ans = 0;
while( ss >> tmp ) ans+=tmp;
cout << ans;
}
真相是什麼?
std::flush
清除輸出串流內的資料
如果操作的對象是檔案,那這個策略是必要的
因為硬碟動一次的代價很大,不一次做多一點事會很浪費時間
std::flush
大部分的OJ都會把標準輸出變成檔案輸出,讓問題更加明顯
可是哪裡用了flush ?
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a;
while( cin >> a )
{
cout << a << endl;
}
}
哪裡用到了flush ?
根據C++的標準,用一次cout<<endl等於
cout << '\n' << flush;
從今天開始,不要亂用endl,用 '\n'代替它
如果不用這個同步功能,可以把它關掉
ios::sync_with_stdio(false);
// 關掉後就不可以cout / printf 混用
因為OJ不會即時的看答案對不對,因此不需要這個功能
因此可以取消這功能
cin.tie(0);
// 關掉後 cout的資料就不一定會立刻顯示
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int a;
while( cin >> a )
{
cout << a << '\n';
}
}
stdio.h | cstdio |
string.h | cstring |
stdlib.h | cstdlib |
ctype.h | cctype |
XXX.h \(\Rightarrow\) cXXX
Undefined Behavior
cout << 1 / 0 << endl;
int x = 2147483647; //int 最大值
if( x+1 > x )
cout << "Ya!" << endl;
else
cout << "溢位" << endl;
int 加超過範圍會變成負數,正確嗎?
if( x+1 > x )
當x不是最大值時,判斷式成立 ( true )
當x是最大值時,雖然二補數下+1會變負數,但是C++規定int計算超出範圍是未定義行為,數值未定。因此編譯器為了優化,可以認為此時 x+1 會大於 x ( true )
if( x+1 > x )
cout << "Ya!";
else
cout << "溢位";
if( true )
cout << "Ya!";
else
cout << "溢位";
cout << "Ya!";
01000000010010001111010111000011
若能避免使用小數,就應該避免
否則要小心處理誤差的問題
double x;
cin >> x;
if( x==5 ); 容易出問題
if( std::abs( x - 5 ) < 1E-6 ); 要容許一點誤差
C=243, A=3, B=?
double A, C;
cin >> C >> A;
int B = log(A) / log(C);
cout << B;
243 3
輸入
輸出
4
log(243)/log(3) = 4.99999999999999911182158029987
計算結果比原來數字小了一點
轉成int,小數部分全部變不見
資料數量 | |
---|---|
struct/class | 任意多 |
std::pair | 2 |
std::tuple | 任意多 |
//定義
struct Point{
int x;
int y;
string name;
};
//宣告
Point data, array[666];
//使用變數
data.x = data.y = array[2].x = 7122;
data.name = "StarStar";
struct Info{
string name;
Info(){ //建構子
name = "7122";
}
};
Info data; //會呼叫建構子
cout<< data.name ;
Output:
7122
struct Point{
int x;
int y;
Point(int a,int b){ //建構子
x=a; y=b;
}
};
Point data(94,87); //會呼叫建構子
cout<< data.x << ' ' <<data.y ;
Output:
94 87
struct Point{
int x;
int y;
Point(int a,int b){ //建構子
x=a; y=b;
}
};
Point data; //ERROR!
Output:
Compile Error!
Can not find Point()
struct Point{
int x;
int y;
Point(int a,int b){ //建構子
x=a; y=b;
}
Point(){ //建構子 called
x=1; y=2;
}
};
Point data;
cout<<x<<' '<<y;
Output:
1 2
using pis = pair<int,string>; //C++11
pii data;
data.first = 123;
data.second = "ABC";
data = {123,"ABC"};
data = make_pair(123,"ABC");
using pii = pair<int,int>;
pii a[3] = { {2,1},{1,2},{3,0} };
sort( a,a+3 );
for( auto v:a )
cout<<v.first<<' '<<v.second<<endl;
Output:
1 2
2 1
3 0
using piii = pair<int,pair<int,int>> ; //C++11
using piii2= pair<int,pair<int,int> > ;
piii a;
a.first = a.second.first = a.second.second = 87
#include<utility> //also in algorithm
using tiii= tuple<int,int,int>;
tiii a;
a = {1,2,3}; // C++14 and g++ >= 6
a = make_tuple(1,2,3); // Always OK
using tiii= tuple<int,int,int>; //C++11
tiii a = {1,2,3};
auto [i,j,k] = a; //C++17
int i,j,k;
tie(i,j,k) = a; //Always OK
i = get<0>(a);
j = get<1>(a);
k = get<2>(a); //Always OK
#include<vector>
vector<int> arr;
arr.resize(N); //設定大小為N
arr[0] = arr[N-1] = 1; //與一般陣列一樣
int a;
cin>>a;
int arr[a]; // Error!
int a;
cin>>a;
vector<int> arr(a);
vector<int> v;
v.push_back(1); //不建議使用
v.emplace_back(2);
for(int i:v) cout<<i<<' ';
Output:
1 2
push_back( 我是資料 )
我是資料 (2)
複製
我是資料 |
---|
複製
push_back要複製資料2次
emplace_back( 我是資料 )
直通終點
我是資料 |
---|
複製
emplace_back要複製資料2次
vector<tuple<int,int,int>> v;
v.push_back(make_tuple(4,5,6));
//只有emplace_back可以這樣縮減歐~
v.emplace_back(7,8,9);
for(auto [i,j,k]:v) //C++17
cout<<i<<' '<<j<<' '<<k<<endl;
Output:
1 2 3
4 5 6
7 8 9
emplace_back的括號可以當作是呼叫建構子