오늘 다루는 분량이 많으니 한 입에 다 먹겠다고 생각하지 마시고, 한 번에 할 수 있는 만큼만 꼭꼭 씹어드세요. 절반만 이해해도 오늘 성공!
다 아시는 얘길테지만 한 번 하고 넘어갑시다
컴퓨터가 하는 많은 일들은 작은 조각들로 쪼갤 수 있죠
1 // 필수 조건: n >= 1 2 // 1 부터 n 까지의 자연수의 합을 반환한다 3 int sum(int n) { 4 int ret = 0; 5 for(int i = 1; i <= n; ++i) 6 ret += i; 7 return ret; 8 }
1 int recursiveSum(int n) { 2 if(n == 1) return 1; // 더 못 쪼개겠네 3 return n + recursiveSum(n-1); 4 }
if
문. 더 이상 쪼개지지 않는 최소의 입력을 처리하죠1 int wrongSum(int n) { 2 if(n == 2) return 3; // 1+2 3 return n + wrongSum(n-1); 4 } 5 6 int correctSum(int n) { 7 if(n == 0) return 0; 8 return n + correctSum(n-1); 9 } 10 11 int correctSum2(int n) { 12 if(n == 0) return 0; 13 if(n == 1) return 1; 14 if(n == 2) return 3; 15 return n + correctSum2(n-1); 16 }
for
대체하기1 for(int i = 0; i < n; ++i) 2 for(int j = i+1; j < n; ++j) 3 for(int k = j+1; k < n; ++k) 4 cout << i << " " << j << " " << k << endl;
for
문을 쓰나?for
문을 쓰나?1 // n: 전체 원소의 수 2 // picked: 지금까지 고른 원소들의 번호 3 // toPick: 더 고를 원소의 수 4 // 일 때, 앞으로 toPick 개의 원소를 고르는 모든 방법을 출력한다. 5 void pick(int n, vector<int>& picked, int toPick) { 6 // 기저 사례: 더 고를 원소가 없을 때 고른 원소들을 출력한다 7 if(toPick == 0) { 8 printPicked(picked); 9 return; 10 } 11 12 // 고를 수 있는 가장 작은 번호를 계산한다 13 int smallest = picked.empty() ? 0 : picked.back() + 1; 14 // 이 단계에서 원소 하나를 고른다 15 for(int next = smallest; next < n; ++next) { 16 picked.push_back(next); 17 pick(elements, picked, toPick - 1); 18 picked.pop_back(); 19 } 20 }
1 int n; // 도시의 수 2 int distance[10][10]; // 두 도시간의 거리를 저장하는 배열 3 4 // 주어진 경로의 길이를 반환하는 함수 5 int getPathLength(const vector<int>& path); 6 // 주어진 경로에 해당 도시가 이미 포함되어 있는지를 반환하는 함수 7 bool isVisited(const vector<int>& path, int city); 8 9 // curPath: 지금까지 만든 경로 위에 있는 도시들의 번호 10 // 라고 할 때, 만들 수 있는 경로 중 가장 짧은 것을 출력한다 11 int shortestPath(vector<int>& curPath) { 12 // 기저 사례: 이미 경로가 완성되었을 때 현재 길이를 반환 13 if(curPath.size() == n) return getPathLength(curPath); 14 int ret = 987654321; 15 for(int next = 0; next < n; ++next) { 16 if(isVisited(curPath, next)) continue; 17 curPath.push_back(next); 18 ret = min(ret, shortestPath(curPath)); 19 curPath.pop_back(); 20 } 21 }
\[ \left({n\atop r}\right)=\begin{cases} \left({n-1\atop r-1}\right)+\left({n-1\atop r}\right)\\ 1 & (r=0\mbox{ or }n=r)\end{cases}\]
구현하는 건 간단하죠?
1 int bino(int n, int r) { 2 // 기저 사례: n=r or r=0 3 if(r == 0 || n == r) return 1; 4 return bino(n-1, r-1) + bino(n-1, r); 5 }
bino(25,12)
를 계산하려면 1천만번의 함수 호출이 필요하다고?1 int cache[100][100]; 2 int bino(int n, int r) { 3 if(n == r || r == 0) return 1; 4 int& ret = cache[n][r]; 5 if(ret != -1) return ret; 6 return ret = bino(n-1, r-1) + bino(n-1, r); 7 } 8 .. 9 memset(cache, -1, sizeof(cache));
1 int counter = 0; 2 int count() { 3 return counter++; 4 }
6
1 2
3 7 4
9 4 1 7
2 7 5 9 4
1 // MAX_NUMBER: 한 칸에 들어갈 숫자의 최대값 2 int n, triangle[100][100]; 3 int cache[100][100][MAX_NUMBER*100+1]; 4 5 // (y,x) 위치까지 내려오기 전에 만난 숫자들의 합이 sum 일 때 6 // 맨 아래줄까지 내려가면서 얻을 수 있는 최대 경로를 반환한다 7 int path1(int y, int x, int sum) { 8 // 기저 사례: 맨 아래 줄까지 도달했을 경우 9 if(y == n-1) return sum + triangle[y][x]; 10 11 // 메모이제이션 12 int& ret = cache[y][x][sum]; 13 if(ret != -1) return ret; 14 15 sum += triangle[y][x]; 16 return ret = max(path1(y+1, x+1, sum), path1(y+1, x, sum)); 17 }
1
2 4
8 16 32
64 128 256 512
1024 2048 4096 8192 16384
path1()
의 입력 $y$, $x$, $sum$ 은 두 부류로 나눌 수 있다path1()
는 $(y,x)$ 에서 내려가는 최대 경로만 찾거든요path1(y,x,sum)
: $(y,x)$ 까지 내려오는 경로의 합이 $sum$ 이었을 때 전체 경로의 최대 합path2(y,x)
: $(y,x)$ 에서 맨 아래까지 내려가는 경로 중 최대 합
이전 조각들에 관한 정보가 없어졌음에 유의합시다:
1 int n, triangle[100][100]; 2 int cache2[100][100]; 3 // (y,x) 위치부터 맨 아래줄까지 내려가면서 얻을 수 있는 4 // 최대 경로의 합을 반환한다 5 int path2(int y, int x) { 6 // 기저 사례 7 if(y == n-1) return triangle[y][x]; 8 9 int& ret = cache2[y][x]; 10 if(ret != -1) return ret; 11 12 return ret = max(path2(y+1, x), path2(y+1, x+1)) + 13 triangle[y][x]; 14 }
\[ path2(y,x)=\begin{cases} \mathit{t}\left[y,x\right]+\max\begin{cases} path2(y+1,x)\\ \mathit{path2}(y+1,x+1)\end{cases} & (y<n-1)\\ t\left[y,x\right] & (y=n-1)\end{cases}\]
Disclaimer: 물론 이것이 전부는 아니고, 경우에 따라서 다를 수도 있고, 운운..
$2 \times N$ 크기의 사각형을 $2 \times 1$ 크기의 타일로 채우는 방법의 수
1 int cache[101]; 2 3 // 2*width 크기의 사각형을 채우는 방법의 수를 반환한다 4 int tiling(int width) { 5 // 기저 사례: width 가 1 또는 0 일 때 6 if(width <= 1) return 1; 7 int& ret = cache[width]; 8 if(ret != -1) return ret; 9 return ret = tiling(width-2) + tiling(width-1); 10 }
\[ climb(d,t) = \begin{cases} 0.75 \times climb(d-1,t-1) \\\\ + 0.5 \times climb(d-1,t-2) \end{cases}\]
--oo
-o-o
-oo-
o--o
o-o-
oo--
\[ \left({n+m\atop n}\right)\begin{cases} \left.\begin{array}{c} \mathtt{-----ooooo}\\ \mathtt{----o-oooo}\\ \mathtt{\vdots}\\ \mathtt{-ooooo----}\end{array}\right\} \left({n+m-1\atop n-1}\right)\\ \left.\begin{array}{c} \mathtt{o-----oooo}\\ \mathtt{o----o-ooo}\\ \vdots\\ \mathtt{ooooo-----}\end{array}\right\} \left({n+m-1\atop n}\right)\end{cases}\]
1 // n개의 -, m개의 o 로 구성된 신호 중 k번째 신호를 반환한다 2 // (k 는 0 부터 시작) 3 string kth(int n, int m, int k) { 4 if(n == 0 && m == 0) return ""; 5 // dashes: - 로 시작하는 신호의 수 6 int dashes = bino[n+m-1][n-1]; 7 if(n > 0 && k < dashes) 8 return "-" + kth(n-1, m, k); 9 return "o" + kth(n, m-1, k - dashes); 10 }
1 map<vector<string>, int> cache; 2 // 내가 이길 수 있으면 1 을, 비길 수 있으면 0 을, 지면 -1 을 리턴한다 3 int canWin(vector<string>& board, char turn) { 4 // 기저 사례: 마지막에 상대가 둬서 한줄이 만들어진 경우 5 if(isFinished(board, 'o'+'x'-turn)) return -1; 6 if(cache.count(board)) return cache[board]; 7 int& ret = cache[board]; 8 // 각 반환값 중 상대가 이기는 결과 (1) 보다 비기는 결과 (0), 9 // 비기는 결과보다 상대가 지는 결과 (-1)를 원한다: 10 // 모든 반환값의 min 을 취하자 11 int minValue = 2; 12 for(int y = 0; y < 3; ++y) 13 for(int x = 0; x < 3; ++x) { 14 if(board[y][x] == '.') { 15 board[y][x] = turn; 16 minValue = min(minValue, canWin(board, 'o'+'x'-turn)); 17 board[y][x] = '.'; 18 } 19 } 20 // 플레이할수 없거나, 어떻게 해도 비기는 것이 최선인 경우 21 if(minValue == 2 || minValue == 0) return ret = 0; 22 // 최선이 상대가 이기는 거라면 난 무조건 지고, 상대가 지는 거라면 난 이긴다 23 return ret = -minValue; 24 }
1 int complexFunction(const string& aString); 2 double moreComplexFunction(const vector<bool>& aBoolVector); 3 long long evenMoreComplexFunction(const vector<int>& anIntVector); 4 double oops(double aDouble);
std::map<>
1 map<string,int> cache1; 2 map<vector<bool>,int> cache2; 3 map<vector<int>,long long> cache3; 4 map<double,double> cache4;
map<double,double>
쓰면 안되지만요)1 // [0,n-1] 범위의 정수에 대응시켜 준다 2 int bijection(const vector<int>& input); 3 4 long long cache[MAX_N]; 5 long long evenMoreComplexFunction(const vector<int>& anIntVector) { 6 long long& ret = cache[bijection(anIntVector)]; 7 if(ret != -1) return ret; 8 ... 9 }
0123456789A$
S = abracadabra$
A = adabra$
B = racada $
\[ [ true, true, false, false, true, true ] \]
\[ = 110011_{2} = 51 \]
1 vector<bool> A; 2 int B; 3 4 if(A[idx]) ; 5 if(B & (1 << idx)) ; 6 7 A[idx] = true; 8 B |= (1 << idx); 9 10 A[idx] = false; 11 B &= ~(1 << idx);
1 2 3 4 5 6
1 2 3 4 6 5
1 2 3 5 4 6
...
6 5 4 3 2 1
1 int n; // 도시의 수 2 int distance[10][10]; // 두 도시간의 거리를 저장하는 배열 3 4 // 주어진 경로의 길이를 반환하는 함수 5 int getPathLength(const vector<int>& path); 6 // 주어진 경로에 해당 도시가 이미 포함되어 있는지를 반환하는 함수 7 bool isVisited(const vector<int>& path, int city); 8 9 // curPath: 지금까지 만든 경로 위에 있는 도시들의 번호 10 // 라고 할 때, 만들 수 있는 경로 중 가장 짧은 것을 출력한다 11 int shortestPath(vector<int>& curPath) { 12 // 기저 사례: 이미 경로가 완성되었을 때 현재 길이를 반환 13 if(curPath.size() == n) return getPathLength(curPath); 14 int ret = 987654321; 15 for(int next = 0; next < n; ++next) { 16 if(isVisited(curPath, next)) continue; 17 curPath.push_back(next); 18 ret = min(ret, shortestPath(curPath)); 19 curPath.pop_back(); 20 } 21 }
curPath
가 사용되는 곳getPathLength
)shortestPath
가 전체 경로 대신 앞으로 지나는 경로의 길이만 반환하도록 바꾸면 이 정보는 필요없어진다curPath.back()
)isVisited
)bool
배열을 받아볼까?bool
배열) 입력으로 바꾸면1 const int MAX = 15; 2 int n, dist[MAX][MAX]; 3 int cache[MAX][1<<MAX]; 4 5 // 현재 위치가 here 이고, visited 가 지금까지 방문한 도시들의 집합일 때 6 // 남은 도시들을 방문할 수 있는 최단 경로의 길이를 반환한다 7 int go(int here, int visited) { 8 // 기저 사례: 전부 방문한 경우 9 if(visited == (1<<n)-1) return 0; 10 // 메모이제이션 11 int& ret = cache[here][visited]; 12 if(ret > 0) return ret; 13 ret = 987654321; 14 for(int there = 0; there < n; ++there) 15 if((visited & (1<<there)) == 0) { 16 int cand = go(there, visited + (1<<there)) + 17 dist[here][there]); 18 ret = min(ret, cand); 19 } 20 return ret; 21 }