C 語言是最早研發出來的高階(使用者友善)語言之一
C++ 是建立在C語言之上,對許多語法和功能進行改良,增加了標準函式庫,是目前競賽程式最常使用的語言
優點:常見,比賽會有
缺點:很容易爛掉,mac 沒有
優點:常見,比賽會有
缺點:很容易爛掉,mac 沒有
HPC, NPSC, APCS, 資芽
#include <iostream>
using namespace std;
int main() {
cout << "Hello World!" << endl;
}
許多功能原本需要加上 std::function_name
using 就是直接告訴編譯器:「跟std名字一樣的東西就是std的」
回傳值:給編譯器看
名字跟參數
程式碼放這
#include <iostream>
using namespace std;
int main() {
}
#include <iostream>
using namespace std;
int main()
{
}
唯一真理
邪教
不是01010111嗎?
雖然電腦裡所有資料都是二進位,程式語言會為使用者做好資料型別(data types),以方便處理不同種類的資料.
名稱 | 大小(位元組) | 數值範圍 |
---|---|---|
int | 4 | -2147483648~2147483647 |
unsigned int | 4 | 0~4294967295 |
long long | 8 | -2^63~2^63 - 1 |
char | 1 | -128~127 (或0~255) |
float | 4 | 3.4E +/- 38 (7 位數) |
double | 8 | 1.7E +/- 308 (15 位數) |
long double | 8 (?) | 1.7E +/- 308 (15 位數) |
bool | 1 | true 或 false |
常見資料型別(不用全背)
int a;
型別
名稱
名稱 | 功能 |
---|---|
賦值(=) | 將左邊的變數數值改成右邊的 |
加(+) | 略 |
減(-) | 略 |
乘(*) | 略 |
除(/) | 會自動取下高斯喔 |
模運算(%) | a % b 為 a 除 b 的餘數 |
在程式裡面輸入兩個數字,輸出他們相加後的平方
int main() {
int a = 5, b = 5;
cout << ????? << endl;
//此處 ? 應為 100
}
int main() {
int a;
cin >> a;
char c;
cin >> c;
int x, y, z;
cin >> x >> y >> z;
}
ex. 寫程式要多練習->true
大括號換行是好事->false
if (條件成立) {
//執行程式
}
裡面的條件其實就是一個布林值!
int a = 7122, b = 7122;
if (a == b) {
cout << "same" << endl;
}
if (a != b) {
cout << "not same" << endl;
}
== 會判斷兩邊的東西是否相同
!= 會判斷兩邊的東西是否不相同
名稱 | 功能 | 舉例 |
---|---|---|
!= | 判斷兩者是否不同 | 5 != 8 |
< | 左<右 | 7 < 9 |
<= | 左<=右 | -5 <= -5 |
> | 左>右 | 6 > 4 |
>= | 左>=右 | 8 >= 8 |
int a = 7122, b = 7122;
if (a == b) {
cout << "same" << endl;
}
if (a != b) {
cout << "not same" << endl;
}
顯然,要不然就\(a\)跟\(b\)一樣,要不然就不一樣,幹嘛寫兩次?
int a = 7122, b = 7122;
if (a == b) {
cout << "same" << endl;
}else {
cout << "not same" << endl;
}
顯然,要不然就\(a\)跟\(b\)一樣,要不然就不一樣,幹嘛寫兩次?
int a = 7122, b = 7122;
if (a == b) {
if (a == 7122) {
cout << "INFOR" << endl;
} else {
cout << "same" << endl;
}
}else {
cout << "not same" << endl;
}
假設我有兩個條件要同時判斷呢?
(\(a == b\) 且 \(a == 7122\))
可以寫兩次 if !
名稱 | 白話文 | 舉例 |
---|---|---|
and (&&) | 左右都對 | true && true == true, false && true == false |
or (||) | 其中一個對 | true || false = true, false || false = false |
not (!) | 對變錯,錯變對 | !true = false, !false = true |
xor (^) | 左右不相同 | true ^ false = true, true ^ true = false |
int a = 7122, b = 7122;
if (a == b && a == 7122) {
cout << "INFOR" << endl;
}
if (a == b) {
cout << "same" << endl;
}else {
cout << "not same" << endl;
}
還可不可以更精簡一點?
int a = 7122, b = 7122;
if (a == b && a == 7122) {
cout << "INFOR" << endl;
} else if (a == b) {
cout << "same" << endl;
} else {
cout << "not same" << endl;
}
a == 7122 | a != 7122 | |
---|---|---|
b == 7122 | GOOD | X |
b != 7122 | X | BAD |
int main() {
int a, b;
cin >> a >> b;
if (a == 7122 && b == 7122) {
cout << "GOOD" << endl;
}
if (a != 7122 && b != 7122) {
cout << "BAD" << endl;
}
}
while (條件成立) {
//做事
}
int a = 5;
while (a > 0) {
cout << a << endl;
a = a - 1;
}
輸入一個正整數\(n\),當那個整數還是偶數的時候,將他除以2,除到不能再除,最後輸出\(n\)
ex:\( n = 28, \ 28 / 2 = 14, \ 14 / 2 = 7\)
不覺得有時候只要重複幾次怎麼辦?
for (起始條件; 判斷條件; 更新動作) {
//做事
}
for (int i = 0;i < 10;i++) {
cout << i * i << endl;
}
/*
輸出
1
4
9
16
25
36
49
64
81
100
*/
int n;
cin >> n;
for (int i = 0;i < n;i++) {
cout << i * i << endl;
}
/*
輸出
1
4
9
16
...
n^2
*/
輸入一個整數,輸出那個整數以下(包含自己)所有5的倍數(從5 開始算5, 10, 15)
ex: 輸入 13,輸出5, 10
輸入20,輸出5, 10, 15, 20
輸入一個整數,判斷該數字是不是完全平方數。
是的話輸出YES,否則輸出NO
ex: 輸入 13,輸出NO
輸入25,輸出YES
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1;5 * i <= n;i++) {
cout << 5 * i << endl;
}
}
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int ans = 0;
for (int i = 1;i <= n;i++) {
if (i * i == n) {
ans = 1;
}
}
if (ans == 1) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int ans = 0;
for (int i = 1;i <= n;i++) {
if (i * i == n) {
ans = 1;
}
}
if (ans == 1) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int ans = 0;
for (int i = 1;i <= n;i++) {
if (i * i == n) {
ans = 1;
break;
}
}
if (ans == 1) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
#include <iostream>
using namespace std;
int main() {
int tens = 0;
for (int i = 0;i <= 100;i++) {
if (i % 10 != 0) {
continue;
}
tens++;
}
}
for (int i = 0;i < 10;i++) {
for (int j = 0;j < 10;j++) {
cout << i * 10 + j << endl;
}
}
#include <iostream>
using namespace std;
int main() {
for (int i = 1;i <= 5;i++) {
for (int j = 1;j <= i;j++) {
cout << j << " ";
}
cout << endl;
}
}
會輸出什麼呢?
輸入一個整數,之後按照以下規則改變他:
如果他目前是偶數,將他除以 2
如果他是奇數,將他乘以3之後加上1
重複執行直到該整數變成1,並列印出過程
ex(26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1)
已知數列的前四項,填出第五項。
已經知道這些數列只可能是等差或等比數列。
(數列的前五項均為不大於105的自然數,等比數列的比值也是自然數)
ex. 1, 3, 5, 7 --> 9
1, 2, 4, 8 --> 16
int arr[4]={7,1,2,2};
arr[0] | arr[1] | arr[2] | arr[3] |
---|---|---|---|
7 | 1 | 2 | 2 |
記得是從零開始算
int arr[4]={7,1,2,2};
arr[3]=5;
for(int i=0;i<4;i++){
cout<<arr[i]<<" ";
}
//7 1 2 5
int n, arr[10];
cin>>n;
for(int i=0;i<n;i++){
cin>>arr[i];
}
陣列大小要是個常數,不能是變數
雖然新版C++好像開放可以用了
不過還是不要用比較好
還有如果陣列開過大(\(10^5\)以上)
可以開全域(main函數外面)
比較不會爆記憶體,而且可以不用初始化
你有一些陣列,這些陣列排成一個陣列
於是,你就得到了二維陣列
1 | 2 | 3 |
---|---|---|
4 | 5 | 6 |
7 | 8 | 9 |
int arr[3][3]
={{1,2,3},
{4,5,6},
{7,8,9}};
int n, m, arr[10][10];
cin>>n>>m;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
arr[i][j]=i-j;
}
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cout<<arr[i][j]<<" ";
}
cout<<"\n";
}
0 | -1 | -2 | -3 | -4 |
---|---|---|---|---|
1 | 0 | -1 | -2 | -3 |
2 | 1 | 0 | -1 | -2 |
3 | 2 | 1 | 0 | -1 |
4 | 3 | 2 | 1 | 0 |
你有一些二維陣列,這些二維陣列排成一個陣列
於是,你就得到了三維陣列
繼續下去還可以得到四維、五維...n維陣列
不過維度也不用太多,夠用就好
題目中跟序列有關的東西都可以用陣列儲存
然後再搭配前面的迴圈、if/else,
就可以解決很多基礎題目。
Q1:GreenJudge上的簡單題
給定\(N\)、\(M\)以及長度為\(M\)的序列\(A\),
請輸出\(1\)~\(N\)中有哪些數字沒在\(A\)中出現
Q2:ZeroJudge上的簡單題
輸入一個矩陣,輸出其翻轉的結果
Q3:Codeforces上的簡單題
給定一個只有0或1的序列,每次可以將一段連續的1往左或右移一格,求最少需要幾次操作才能使所有1合併成連續的區間
Q4: Bubble Sort
給定一個長度為\(N\)正整數序列,將其由小到大排序
想法: 從左到右依序考慮每對相鄰的數,
如果左邊比右邊大就交換,
於是最大的一定會到最右邊,
所以重複\(N\)次就做完了。
要跑多久?
複雜度分析,二分搜技巧
這些表示方法有哪些優缺點呢?
如果問題的大小不固定要怎麼解釋程式的效率?
程式執行時間:
問題大小 \(\times\) 時間複雜度 \(\times\) 運算次數的常數 \(\times\) 單位運算時間
問題大小 \(\times\) 時間複雜度 \(\times\) 運算次數的常數 \(\times\) 單位運算時間
對於一個比較大的複雜度
只要問題大小(n)夠大,計算量一定比複雜度小的還多!
階乘 > 指數 > 多項式(次方越大就越大)> 對數 > 常數\(O(1)\)
還有期望複雜度、均攤複雜度 (課外)....
int n;
cin >> n;
for (int i = 0;i < n;i++) {
for (int j = 0;j < i;j++) {
cout << 7122;
}
}
int n;
cin >> n;
for (int i = 2;i <= n;i++) {
for (int j = 2;j * j <= n;j++) {
cout << 7122;
}
}
\(O(n^2)\)
\(O(n \sqrt{n})\)
int n;
cin >> n;
int cnt = 0;
while (n > 0) {
cnt++;
n /= 2;
}
cout << cnt << endl;
int n;
cin >> n;
for (int i = 1;i <= n;i++) {
for (int j = i;j <= n;j += i) {
cout << j << endl;
}
}
\(O(\log n)\)
\(O(n\log n)\)
那要怎麼讓範圍快一點縮小?
從這裡切的話,不管怎麼樣範圍都會小一半
平均下來只要切 \(O(\log n)\)次!
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n; // number from 1~n
int low = 1, up = n + 1, mid;
while (up > low) { //[low, up)
mid = (low + up) / 2;
cout << "Is " << mid << " bigger (0), smaller (1), or equal (2) to answer?" << endl;
int res;
cin >> res;
if (res == 0) {
up = mid;
} else if (res == 1) {
low = mid + 1;
} else {
cout << "The answer is " << mid << endl;
break;
}
}
}
來看看他的一些應用...
比他小的
不比他小的
而這個判斷條件可以換成任何一個布林值!
可以做到 v.s. 不能做到
大於答案 v.s. 小於答案
那再用剛剛的圖示
贏不了
贏得了
也就是說,如果我能夠知道過了某個天數能不能贏,我就可以快速(\(O(\log C)\)) 次知道這個分界點!
那他的複雜度會是 \(O(n\log n)\)檢查
總複雜度 \(O(n \log^2 n)\) 喔!(死
給定一個數線上的\(N\)個點,求在數線上選取\(K\)個點時,被選取的點兩兩之間的距離最大可以是多少。
\(k \leq N \leq 10^5\)
通常二分搜的題目會長這樣: 找到最大(最小) 的數字,使得他符合(不符合)某種條件。
觀察看看他的單調性!
如果我們看答案,那我們要求的答案越小,越有可能會可以選到 \(k\)個東西!
那要怎麼檢查能不能放 \(k\) 個東西呢?
數線上有 \(n\)個郵筒,每個點座標\(a_i\),你有\(k\)個郵差,每個郵差至多可以負責 \(m\)個郵筒。定義一個郵差的「投遞範圍」為(他負責最右邊的郵筒 - 最左邊的郵筒)。在這些限制下,所有郵差投遞範圍最大值最小可以是多少?
ex. (A -> 1, 3,B -> 5, 7, 8,C -> 9
答案為\(\max(3 - 1, 8 - 5, 9 - 9) = 3\))
答案越大,越有可能分好所有的郵差
先選前面的,紀錄目前選的個數。只要個數超過\(m\)或者距離超過限制,就是不可能達到
mid必須要可以跑到所有需要的範圍 [答案可能最小值~答案可能最大值]
記得在 c++ 的除法是無條件捨去
區間的範圍 (左閉右開,左閉右閉,左開右開)
紀錄的答案是甚麼意思
\(0, 0, ..., 0, 0, 1, 1, 1, ..., 1\)
這個 或這個?
二分搜可以很簡單,也可以超難
大家有沒有覺得演算法很有趣呢?
那我們有什麼辦法獲得這個「記憶體位置資訊」呢?
int main() {
int a = 3;
cout << &a << endl;
}
#include <iostream>
using namespace std;
int main() {
int a = 3;
int * point = &a;
cout << point << endl;
}
雖然符號一樣,不過位置不同有不同的功能!
(一個用於宣告,一個用於取值)
#include <iostream>
using namespace std;
int main() {
int a = 3;
int * point = &a;
//這是定義用的*
cout << *point << endl;
//這是取值用的*
}
會輸出什麼?
#include <iostream>
using namespace std;
int main() {
int a = 3;
int &point = a;
cout << point << endl;
}
容器類別<資料型別> 容器名稱;
大致可分為隨機存取的(直接取某項)
與只能往前或往後走一格的
#include <iostream>
#include <vector>
using namespace std;
vector<int> v;
int main() {
v.push_back(7122);
cout<<v[0]<<"\n";
}
vector<int> v;
for(int i=0;i<v.size();i++){
cout<<v[i]<<" ";
}
/*--------------------------*/
for(auto i:v){
cout<<i<<" ";
}
/*--------------------------*/
for(auto i=v.begin();i!=v.end();i++){
cout<<*i<<" ";
}
#include <iostream>
#include <utility>
using namespace std;
int main(){
pair<int,int> p = {71,22};
cout<<p.first<<" "<<p.second<<"\n";
}
#include <iostream>
#include <utility>
using namespace std;
typedef pair<int,int> pii;
#define F first
#define S second
int main(){
pii p = {71,22};
cout<<p.F<<" "<<p.S<<"\n";
}
#include <iostream>
#include <algorithm>
using namespace std;
int main(){
int a, b;
cin>>a>>b;
cout<<max(a,b)<<"\n";
}
給定一個長度為\(N\)正整數序列,將其由小到大排序
想法: 從左到右依序考慮每對相鄰的數,
如果左邊比右邊大就交換,
於是最大的一定會到最右邊,
所以重複\(N\)次就做完了。
要跑多久?
#include <iostream>
#include <algorithm>
using namespace std;
int arr[100];
int main(){
sort(arr,arr+100);
}
複雜度: \(O(NlogN)\)
#include <iostream>
#include <algorithm>
using namespace std;
int arr[100];
bool cmp(int a, int b){
return a>b;
}
int main(){
sort(arr,arr+100,cmp);
}
當然,比較方式也可以全照自己高興
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> v;
int main(){
cout<<lower_bound(v.begin(),v.end(),7122)-v.begin();
}
時間複雜度: 隨機存取迭代器\(O(logN)\),其他\(O(N)\)
#include<bits/stdc++.h>
5 | 8 | 7 | 4 |
---|
10
3
10
3
#include <iostream>
#include <queue>
using namespace std;
queue<int> que;
int main(){
que.push(5);
que.push(3);
cout<<que.front(); //5
que.pop();
cout<<que.front(); //3
}
5 | 8 | 7 | 4 | 10 |
---|
3
3
6
6
#include <iostream>
#include <stack>
using namespace std;
stack<int> stk;
int main(){
stk.push(5);
stk.push(3);
cout<<stk.top(); //3
stk.pop();
cout<<stk.top(); //5
}
#include <iostream>
#include <queue>
using namespace std;
deque<int> deq;
int main(){
deq.push_front(5);
deq.push_back(3);
cout<<deq.front(); //5
cout<<deq.back(); //3
deq.pop_front();
deq.pop_back();
}
5
5
5
5
5
5
#include <iostream>
#include <queue>
using namespace std;
deque<int> deq;
int main(){
deq.push_front(5);
deq.push_back(3);
cout<<deq.front(); //5
cout<<deq.back(); //3
deq.pop_front();
deq.pop_back();
}
其中 \(f\) 稱為函數
\(x\) 是輸入值
\(y\) 是輸出值
ex. \(f(x) = 71x + 22, f(a, b) = 3a^3 - b^2\)
int func(char input) {
// do stuff
return output;
}
函數名稱:func
輸入:input
輸入型別:char
輸出:output
輸出型別:int
#include <iostream>
using namespace std;
int func(char input) {
// do stuff
return output;
}
int main() {
}
一般是寫在 #include 之後,main 函式前面
#include <iostream>
using namespace std;
int func(char input) {
// do stuff
return output;
}
int main() {
cout << 2 + func('a') << endl;
}
把他當成是「回傳型別」的那個東西來用
#include <iostream>
using namespace std;
int f(int a) {
return 2 * a + 1;
}
int main() {
cout << f(3) << endl; //7
cout << f(5) << endl; //11
cout << f(f(3) + 3); //21
}
以下程式每一行分別會輸出什麼?
#include <iostream>
using namespace std;
int f(int a, int b) {
return (a + 3) * (b + 2);
}
int main() {
cout << f(3, 4) << endl; //36
cout << f(f(1, 2), 3) << endl; //95
}
以下程式每一行分別會輸出什麼?
#include <iostream>
using namespace std;
int f(int a) {
return a + 2;
}
int g(int a) {
return a / 2;
}
int main() {
cout << f(3 + g(5)) << endl; //7
cout << f(g(f(10))) << endl; //8
}
如果有好幾個函式呢?
注意到:當我們有好幾的函式包在一起的時候,計算順序是
由內而外
Hint: \((x1 - x2)^2 + (y1 - y2)^2\)
int dist(//four numbers) {
return .....
}
int main() {
cout << dist(1, 0, 2, 2) << endl;
//5
cout << dist(3, 2, 1, 2) << endl;
//4
}
適用於函數沒有回傳值的時候
void printarr(vector<int> v){
for(int i=0;i<v.size();i++){
cout<<v[i]<<" ";
}
cout<<"\n";
}
int f(int x) {
return f(x - 1) + 1;
}
int f(int x) {
return f(x - 1) + 1;
}
這樣其實會跑不完www
int f(int x) {
if (x <= 0) return 0;
return f(x - 1) + 1;
}
呼叫\(f(5)\)時會輸出什麼呢?
int fib(int x) {
if (x == 0) return 1;
if (x == 1) return 1;
return fib(x - 1) + fib(x - 2);
}
\(gcd(0, b) = b\)
\(gcd(a, b) = gcd(b \ mod \ a, a) \)
證明...有時間再說吧XD
輸入一個數字 \(x(-300 < x < 300)\) ,請計算 \(f(x)\)。
把左邊的每個環移到中間,一次只能搬一個環,而且上面的環一定要比下面小。
相鄰(相近)的字元會在ASCII的相鄰位置!
其實有欸
'\n' -> 換行
'\t' -> tab
'\\' -> \
int main() {
cout << char(67) << endl;
cout << int('A') << endl;
cout << char('A' + 5) << endl;
}
注意,字元之間本來就可以跟整數/其他字元相加!
int main() {
char c;
cin >> c;
if ('0' <= c && c <= '9') {
cout << "is digit" << endl;
} else {
cout << "not digit" << endl;
}
}
int main() {
char str[100];
cin >> str; //or scanf("%s", str)
cout << str << endl;
}
注意,超過一百的字元的話會爆掉
遇到空白或換行就會停掉
int main() {
char str[100];
for (int i = 0;i < 100;i++) str[i] = 0;
cin >> str; //or scanf("%s", str)
for (int i = 0;i < 100;i++) {
if (str[i] == 0) {
cout << "length " << i << endl;
break;
}
}
}
int main() {
string s;
cin >> s;
cout << s << endl;
}
int main() {
string s;
cin >> s;
cout << s.size() << endl;
s += "7122";
for (int i = 0;i < s.size();i++) {
cout << s[i] << endl;
}
sort(s.begin(), s.end());
reverse(s.begin(), s.end());
cout << s << endl;
}
取長度: s.size()
後面加東西: +=
排序:sort
倒過來:reverse
int main() {
string s;
char arr[100];
cin.getline(arr); //for 字元陣列
getline(cin, s);
}
struct object{
int a, b;
char c;
};
struct object{
int a, b;
char c;
int sum() {
return a + b;
}
};
struct object{
int a, b;
char c;
object() {
a = 0, b = 0, c = 'a';
}
object(int d) {
a = d, b = d + 5, c = 'A';
}
void QQ() {
cout << a + b << " QQ~" << endl;
}
};
int main() {
obj ob = object(4);
ob.QQ();
}
struct object{
int a, b;
char c;
object() {
a = 0, b = 0, c = 'a';
}
object(int d) {
a = d, b = d + 5, c = 'A';
}
};
int main() {
object obj = object(3);
obj.b += 4;
cout << obj.b << endl;
}
2020全國賽pA
\(n \leq 10^5, w_i, v_i, C \leq 10^9\)
例如:
1 | 2 | 3 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 5 |
假設數字由小到大排好呢?
1 | 1 | 3 | 3 | 3 | 4 | 5 | 5 |
---|
1 | 1 | 3 | 3 | 3 | 4 | 5 | 5 |
---|
只要「前面有重複」,那麼增加一個東西一定不會把答案變差!
//Challenge: Accepted
#include <iostream>
#define maxn 100005
#define Goodbye2020 ios_base::sync_with_stdio(0);cin.tie(0);
using namespace std;
int a[maxn];
int main() {
Goodbye2020
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 0;i < n;i++) cin >> a[i];
for (int i = 1;i < n;i++) {
if (a[i] <= a[i - 1]) a[i]++;
}
int ans = 1;
for (int i = 1;i < n;i++) {
if (a[i] > a[i - 1]) ans++;
}
cout << ans << "\n";
}
return 0;
}
\(N\leq 50\)
long long fib(int n){
if(n==0) return 0;
if(n==1) return 1;
return fib(n-1)+fib(n-2);
}
複雜度?
大約是\(O(2^N)\)
fib(4)
fib(3)
fib(2)
fib(1)
fib(1)
fib(0)
fib(2)
fib(1)
fib(0)
fib(5)
fib(3)
fib(2)
fib(1)
fib(1)
fib(0)
同樣的東西重複算太多次
long long dp[51];
long long fib(int n){
if(n==0) return 0;
if(n==1) return 1;
if(dp[n]]>0) return dp[n];
return dp[n]=fib(n-1)+fib(n-2);
}
long long dp[51];
int main(){
int n;
cin>>n;
dp[0]=0; dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
cout<<dp[n]<<"\n";
return 0;
}
有很多子問題可歸類為同樣的問題
子問題的解在確定後就不受其他決策影響
問題的最優解,是由子問題的最優解合併而來
如何記錄子問題的答案
問題的答案如何藉由子問題求得
遞迴終點 (Base case)
同樣的fib(x)被算到很多次
fib(x)的答案算完後就確定
fib(x)總是等於fib(x-1)+fib(x-2)
dp[i]=費氏數列第 i 項
dp[i]=dp[i-1]+dp[i-2]
dp[0]=0, dp[1]=1
狀態: \(O(N)\)
轉移: \(O(1)\)
總複雜度: \(O(N)\)
題外話: 使用某些技巧可以優化至\(O(logN)\)
再使用某些技巧可以優化至\(O(1)\)(存月球)
有一個長度為\(N\)的整數序列,求總和最大的連續子序列(可以為空)的總和
\(N\leq 10^6\)
dp[i]=以第 i 項結尾的最大連續和
答案為 \(\max_{i=0}^{n} dp[i]\)
dp[i]=max(dp[i-1],0)+arr[i]
dp[0]=0 (1-base)
const int MAXN=1E6+10;
int arr[MAXN];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>arr[i];
}
int sum=0, ans=0;
for(int i=1;i<=n;i++){
if(sum<0) sum=0;
sum+=arr[i];
ans=max(ans,sum);
}
cout<<ans<<"\n";
return 0;
}
有 \(N\) 個硬幣,第 \(i\) 個硬幣面額為 \(w_i\),求能否湊出 \(K\) 元
\(N,K\leq 5000\)
dp[i][j] = 使用前 i 種硬幣能否湊出 j 元
答案為 dp[n][k]
dp[i][j] = dp[i-1][j] | dp[i-1][j-w[i]]
dp[0][0]=1
剛剛的湊零錢問題,可以發現dp[i]的值只跟dp[i-1]有關
當我們把dp[i]的值都算好,dp[i-1]的值就可以丟掉了
空間複雜度節省至O(K)
此技巧稱為滾動dp
有 \(N\) 種硬幣,第 \(i\) 種硬幣面額為 \(w_i\),求湊出 \(K\) 元所需最少硬幣數,若無法達成輸出-1
\(N,K\leq 5000\)
dp[i][j] = 使用前 i 種硬幣湊出 j 元所需最少硬幣數
答案為 dp[n][k]
dp[i][j] = min(dp[i-1][j], dp[i-1][j-w[i]]+1)
dp[0][0]=1,其他設為無限大
有 \(N\) 個物品,第 \(i\) 個物品有重量 \(w_i\) 與價值 \(v_i\) ,你有一個容量為\(M\)的背包,求在物品重量總和不超過\(M\)的前提下,所能得到的最大價值總和
\(N,M\leq 5000\)
dp[i][j] = 使用前 i 個物品,在重量不超過 j 的情況下所能得到的最大價值
答案為 dp[n][m]
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
設為全0
有一個長度為 \(N\) 的序列,第 \(i\) 項為 \(a_i\),求最長遞增子序列的長度
\(N\leq 5000, a_i\leq 10^9\)
dp[i]: 以第 i 項結尾的LIS長度
答案為 \(\max\limits_{1\leq i\leq n} dp[i]\)
\(dp[i] = \max\limits_{j<i, a_j<a_i} dp[j]+1\)
設為全0
有一個長度為 \(N\) 的序列,第 \(i\) 項為 \(a_i\),求最長遞增子序列的長度
\(N\leq 10^5, a_i\leq 10^9\)
dp[i][j]: 考慮序列的前 i 項,長度為 j+1 的LIS中,最後一項的最小值。若不存在則設為無限大。
答案為dp[n]答案為無限大的最小 j。
\(dp[i][j] = a_i\) if \(a_i\geq dp[i-1][j-1]\And a_i<dp[i-1][j]\)
\(dp[i-1][j]\) else
全設為無限大
將 i 維度滾掉,空間降至O(N)
比較長的LIS,最後一項一定比較大
dp[i-1]中,第一個大於\(a_i\)的項會改成\(a_i\)
二分搜找到那一項
有兩個長度為 \(N\) 的序列,求兩序列的最長公共子序列的長度
\(N\leq 5000\)
dp[i][j]: 考慮第一個序列前 i 項與第二個序列前 j 項,他們的LCS長度
\(dp[i][j] = \max(dp[i-1][j-1]+1\), if \(a_i==b_j\)
\(\max(dp[i-1][j], dp[i][j-1]))\)
設為全0
Divide and Conquer
又或是可以這麼理解
有很多子問題可歸類為同樣的問題
子問題的解在確定後就不受其他決策影響
問題的最優解,是由子問題的最優解合併而來
分治切出來的小問題通常不會算到同一個
以下會比較兩種常見的做法:
泡沫排序 (Bubble Sort)
合併排序 (Merge Sort)
Ans: 從右邊做到左邊,如果左邊的數字比右邊大就交換,否則就 break。
複雜度 \(O(n^2)\)
1. 把大小為\(n\)的問題切成大小為\(n - 1\)和\(1\)的問題。
2. 遞迴解決(只剩一個數字時就不用排序了)
3. 合併(交換)
切的方法感覺不太平均?
假設有兩個排序好的序列 \(a, b\),要如何把他們合併成一個排好的序列?
1 | 3 | 5 | 8 | 9 |
---|---|---|---|---|
1 | 2 | 2 | 7 |
1
1
2
2
3
5
7
8
9
可以在\(O(|a| + |b|) = O(n)\)之內做完!
有 O(logn)層
每層都是O(n)
共O(nlogn)
實際的概念就是對於每個數字\(x\),找到有幾個數字序列位置在他左邊而且比他小。
序列長度 \(\leq 2 \times 10^5\)
提示:利用 merge sort的方法!
1. 把序列切成兩半,有三種狀況
遞迴解決
已經保證右邊的序列數字都比左邊序列的數字還右邊了
在放右邊數字的時候,比他小的左邊數字一定被放過,比他大的一定沒有!
只要記錄合併時目前有幾個左邊的東西被放置就好!
有一個 \(n\)列\(m\)行 的正整數矩陣,要問你每ㄧ列的最大值,但你不知道矩陣長什麼樣子,只能詢問某一個位置的數值。保證每一列的最大值位置嚴格遞增。
這題好難><
我好像也不太會做
最簡單的作法: \(O(b)\)次乘法
可不可以更快?
有想到甚麼嗎?
我能在\(O(\log b)\)找出\(a^1, a^2, a^4, a^8, ..., a^{2^k}\)次方的值,再利用\(b\)的二進位表示法湊出答案!
這也可以用在矩陣上喔
求出費式數列的第\(n\)項模\(10^9 + 7\),\(n \leq 10^{18}\)
顯然\(O(n)\)太慢了?
矩陣與線性變換?
矩陣乘法?
大概就是快速的做乘法?
大概就是快速的做乘法?