"현재 상태에서 가능한 모든 선택지를 다 플레이해보는 방법"
-> 난 지금 g++로 컴파일 중이었는데
alias g++
g++='g++ -std=c++17 -pedantic'
이렇게 컴파일 옵션을 설정하고 있었다. 이 경우 최적화 없이 디버깅 모드로 컴파일 되는 거였음.
g++ -O2
그래서 이렇게 해주면 된다.
g++ 기본 설정은 디버그용(-O0)이라 느립니다. 성능을 최대로 끌어내기 위해선 Release 모드, 즉 최적화 옵션을 활성화해야 합니다.
-> 출처
시간 측정은 time으로 해주면 된다.
#include <bits/stdc++.h>
using namespace std;
int arr[10];
bool isused[10];
int n, m;
void f(int k) {
if (k == m) {
for (int i = 0; i < m; ++i) cout << arr[i] << ' ';
cout << '\n';
return;
}
for (int i = 1; i <= n; ++i) {
if (isused[i]) continue;
arr[k] = i;
isused[i] = 1;
f(k + 1);
isused[i] = 0;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
f(0);
}
뭔가 이제 감으로는 확실히 알겠는데.. 내가 풀어놓고도 어케 푼 건지 모르겠다
일단 수열이 각 상태가 1부터 n까지 바뀌어야 하는 건 알겠음 그래서 for (int i = 1; i <= n; ++i).
그리고 각 시작점에 대해서 arr을 채워나가야 함. 이때 중복체크를 해줘야 하기 때문에 arr[k] = i; isused[i] = 1;을 해줌.
그리고 다음 k+1번째 배열을 채워주기 위해 f(k+1)로 들어감.
이때 이미 i가 사용된 경우를 피해줘야 하기 때문에 if(isused[i]) continue;를 처음에 넣어줌.
그렇게 타고타고 가다가 k == m이 되는 순간 채워진 배열을 출력하고 return 돼야 함.
들어갔던 함수를 나오면서 isused[i] = 0; 으로 바꿔줘야 함. 내가 쓰인 순간들은 모두 끝났기 때문에.
🔁 너의 설명을 기반으로 구조화한 정리
1. f(k)는 수열의 k번째 자리를 채우는 함수이다.
2. for (int i = 1; i <= n; ++i)를 통해 1부터 n까지의 숫자를 하나씩 넣어본다.
3. 이미 사용한 숫자는 isused[i] == true이므로 continue로 건너뛴다.
4. 사용하지 않은 숫자라면 arr[k] = i, isused[i] = true로 해당 숫자를 채운다.
5. f(k + 1)을 호출해서 다음 자리를 채운다.
6. 함수가 끝나고 나면 다시 isused[i] = false로 되돌려서 다른 경우의 수를 위한 준비를 한다.
7. k == m이 되면 m개의 수를 모두 고른 상태이므로 출력하고 리턴한다.
#include <bits/stdc++.h>
using namespace std;
int cnt;
int n;
bool isused[3][35];
void f(int r) {
if (r == n) {
++cnt;
return;
}
for (int c = 0; c < n; ++c) {
if (isused[0][c] || isused[1][r + c] || isused[2][(r - c) + (n - 1)])
continue;
isused[0][c] = 1;
isused[1][r + c] = 1;
isused[2][(r - c) + (n - 1)] = 1;
f(r + 1);
isused[0][c] = 0;
isused[1][r + c] = 0;
isused[2][(r - c) + (n - 1)] = 0;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
f(0);
cout << cnt;
}
N-Queen은 언제 안 까먹을까...
isused 배열이 3개 필요하다. |
, \
, /
. 즉 수직, 대각선1, 대각선2를 보고 가지치기를 결정하기 위해.
일단 |
는 쉽다. 내 col을 그대로 인덱스로 사용해주면 된다.
/
도 쉽다. 내가 x,y라면 (x+1, y-1), (x+2, y-2) ... 이런식으로 대각선 좌표가 형성되니까 r+c
를 인덱스로 사용해주면 된다.
\
는 내가 x,y라면 (x+1, y+1), (x+2, y+2) ... 이런식으로 좌표가 형성된다. r-c가 같게 되는 건데 이때 음수값이 나올 수 있다. 범위가 (-(n-1) ~ +(n-1))
이 된다. 왜냐면 (0, n-1) ~ (n-1, 0)
이니까.
그래서 보정을 위해 r-c + n-1
을 인덱스로 사용해준다.
부분 수열은 내가 포함 돼있냐, 안 돼있냐로 타고타고 들어가야 함!
그리고 공집합도 포함된다는 것 주의
#include <bits/stdc++.h>
using namespace std;
int n, s, cnt;
int arr[20];
void f(int cur, int sum) {
if (cur == n) {
if (sum == s) ++cnt;
return;
}
f(cur + 1, sum); // 내가 포함되지 않음
f(cur + 1, sum + arr[cur]); // 내가 포함 됨
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> s;
for (int i = 0; i < n; ++i) cin >> arr[i];
f(0, 0);
if (s == 0) --cnt; // 공집합의 합도 0이므로
cout << cnt;
}
#include <bits/stdc++.h>
using namespace std;
int n, m;
int ans[10];
void f(int k, int start) {
if (k == m) {
for (int i = 0; i < m; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = start; i <= n; ++i) {
ans[k] = i;
f(k + 1, i + 1);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
f(0, 1);
}
isused 불필요하다는 것 주의!!
이전에 출력된 원소는 자연스럽게 쓰이지 않게 된다.
isused 없이도 조합을 만들 수 있는 이유는,
재귀마다 start를 이전 수 + 1로 올려주기 때문에
이미 선택된 수보다 작은 수는 다시 고려되지 않음.
따라서 자연스럽게 중복 없이 오름차순 조합이 생성된다.
#include <bits/stdc++.h>
using namespace std;
int n, m;
int ans[10];
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
vector<int> a;
for (int i = 0; i < n; ++i) a.push_back(i < m ? 0 : 1);
do {
for (int i = 0; i < n; ++i) {
if (a[i] == 0) cout << i + 1 << ' ';
}
cout << '\n';
} while (next_permutation(a.begin(), a.end()));
}
bool next_permutation(begin, end);
현재 배열을 "다음 사전순 순열"로 바꿔줌
리턴값:
true: 다음 순열이 존재해서 바꿔줬다는 뜻
false: 더 이상 다음 순열이 없다는 뜻 (가장 큰 순열이었다)
조합을 만들기 위해선 뽑을 위치가 0, 안 뽑을 위치가 1이라 생각하고 진행해야 한다.
✅ 주의할 점
입력이 꼭 사전순(오름차순) 으로 정렬되어 있어야
→ 모든 순열을 빠짐없이 생성할 수 있음
// 3개 뽑기 → 0: 뽑음, 1: 안 뽑음
vector<int> a = {0, 0, 0, 1, 1}; // 5C3
do {
for (int i = 0; i < a.size(); ++i)
if (a[i] == 0) cout << i + 1 << ' ';
cout << '\n';
} while (next_permutation(a.begin(), a.end()));
#include <bits/stdc++.h>
using namespace std;
int arr[10];
int n, m;
void f(int k) {
if (k == m) {
for (int i = 0; i < m; ++i) cout << arr[i] << ' ';
cout << '\n';
return;
}
for (int i = 1; i <= n; ++i) {
arr[k] = i;
f(k + 1);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
f(0);
}
간단.
next_permutation으로 풀 수 없음.
중복이 허용된 중복 순열이기 때문에.
next_permutation()은
입력된 배열의 순열을 만드는 함수
즉, 고정된 원소를 섞는 것만 가능하지
새로운 값을 추가하거나 중복을 생성하지는 못함
#include <bits/stdc++.h>
using namespace std;
int n, m;
int arr[10];
void f(int k, int st) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << arr[i] << ' ';
cout << '\n';
return;
}
for (int i = st; i <= n; ++i) {
arr[k] = i;
f(k + 1, i);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
f(0, 1);
}
간단
#include <bits/stdc++.h>
using namespace std;
int n, m;
int arr[10], ans[10];
bool isused[10];
void f(int k) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = 0; i < n; ++i) {
if (isused[i]) continue;
ans[k] = arr[i];
isused[i] = 1;
f(k + 1);
isused[i] = 0;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> arr[i];
sort(arr, arr + n);
f(0);
}
간단
#include <bits/stdc++.h>
using namespace std;
int n, m, arr[10], ans[10];
void f(int k, int st) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = st; i < n; ++i) {
ans[k] = arr[i];
f(k + 1, i + 1);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> arr[i];
sort(arr, arr + n);
f(0, 0);
}
간단
#include <bits/stdc++.h>
using namespace std;
int arr[10], ans[10], n, m;
void f(int k) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = 0; i < n; ++i) {
ans[k] = arr[i];
f(k + 1);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> arr[i];
sort(arr, arr + n);
f(0);
}
간단
#include <bits/stdc++.h>
using namespace std;
int n, m, arr[10], ans[10];
void f(int k, int st) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = st; i < n; ++i) {
ans[k] = arr[i];
f(k + 1, i);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> arr[i];
sort(arr, arr + n);
f(0, 0);
}
간단
#include <bits/stdc++.h>
using namespace std;
int n, m, arr[10], ans[10];
bool isused[10];
void f(int k) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = 0; i < n; ++i) {
if (isused[i]) continue;
if (ans[k] == arr[i]) continue;
isused[i] = 1;
ans[k] = arr[i];
f(k + 1);
isused[i] = 0;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> arr[i];
sort(arr, arr + n);
f(0);
}
반례
4 4
1 1 1 2
지금 나는 ans[k]가 이미 arr[i]와 같은 값으로 채워져 있었다면 이를 피해 중복을 방지하려 했었다.
이러면 문제가
1 1 1 2
1 1 2 1 (여기까진 ok. 백트래킹으로 k == 1로 감)
1 2 1 1 (이걸 해야하는데 이전에 있던 ans[k] 때문에 못 한다 ans[3] == arr[i]가 돼버림)
같은 depth에 있을 때 중복으로 사용하는 경우만 막아야 하는데 다른 depth에서 채웠던 값도 중복이면 오답으로 여겨버려서 문제가 생겼던 것.
같은 depth의 중복을 피하기 위해 last 값을 두고 ans[k]를 채웠을 때마다 update가 되게 해서 검사를 한다.
#include <bits/stdc++.h>
using namespace std;
int n, m, arr[10], ans[10];
bool isused[10];
void f(int k) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
int last = 0;
for (int i = 0; i < n; ++i) {
if (isused[i]) continue;
if (last == arr[i]) continue;
isused[i] = 1;
ans[k] = arr[i];
last = arr[i];
f(k + 1);
isused[i] = 0;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> arr[i];
sort(arr, arr + n);
f(0);
}
#include <bits/stdc++.h>
using namespace std;
int n, m, arr[10], ans[10];
bool isused[10];
void f(int k, int st) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
int last = 0;
for (int i = st; i < n; ++i) {
if (isused[i] || last == arr[i]) continue;
isused[i] = 1;
ans[k] = last = arr[i];
f(k + 1, i + 1);
isused[i] = 0;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> arr[i];
sort(arr, arr + n);
f(0, 0);
}
sort 놓치지 말기!
#include <bits/stdc++.h>
using namespace std;
int n, m, arr[10], ans[10];
void f(int k) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = 0; i < n; ++i) {
ans[k] = arr[i];
f(k + 1);
}
}
bool in(int k, int size) {
for (int i = 0; i < size; ++i) {
if (k == arr[i]) return true;
}
return false;
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
int cnt = 0;
for (int i = 0; i < n; ++i) {
int k;
cin >> k;
if (!in(k, cnt)) arr[cnt++] = k;
}
n = cnt;
sort(arr, arr + n);
f(0);
}
존재 여부 검사
bool exist[10001];
if (exist[k]) continue;
exist[k] = 1;
arr[cnt++] = k;
이렇게 O(1)로 가능하단 것도 잊지 말기
#include <bits/stdc++.h>
using namespace std;
int n, m, arr[10], ans[10];
bool exist[10001];
void f(int k, int st) {
if (k == m) {
for (int i = 0; i < k; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = st; i < n; ++i) {
ans[k] = arr[i];
f(k + 1, i);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
int cnt = 0;
for (int i = 0; i < n; ++i) {
int k;
cin >> k;
if (exist[k]) continue;
arr[cnt++] = k;
exist[k] = 1;
}
n = cnt;
sort(arr, arr + n);
f(0, 0);
}
#include <bits/stdc++.h>
using namespace std;
int arr[15], ans[6], k;
void f(int d, int st) {
if (d == 6) {
for (int i = 0; i < d; ++i) cout << ans[i] << ' ';
cout << '\n';
return;
}
for (int i = st; i < k; ++i) {
ans[d] = arr[i];
f(d + 1, i + 1);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
while (1) {
cin >> k;
if (k == 0) break;
for (int i = 0; i < k; ++i) cin >> arr[i];
f(0, 0);
cout << '\n';
}
}
쉬움
#include <bits/stdc++.h>
using namespace std;
int l, c;
char arr[20], skip[20];
int aeiou() {
int cnt = 0;
for (int i = 0; i < c; ++i) {
if (skip[i]) continue;
char ch = arr[i];
if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u')
++cnt;
}
return cnt;
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> l >> c;
for (int i = 0; i < c; ++i) cin >> arr[i];
sort(arr, arr + c);
for (int i = l; i < c; ++i) skip[i] = 1;
do {
int cnt = aeiou();
if (cnt == 0 || l - cnt < 2) continue;
for (int i = 0; i < c; ++i) {
if (skip[i] == 0) cout << arr[i];
}
cout << '\n';
} while (next_permutation(skip, skip + c));
}
쉬움
으으... 어려웠다.
이건 그냥 25C7을 하고 조건에 맞는지, 아닌지 확인해줬어야 했던 문제.
#include <bits/stdc++.h>
using namespace std;
char student[8][8];
int skip[25], ans;
bool vis[8][8];
queue<pair<int, int> > q;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, -1, 0, 1};
void init_vis() {
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) {
if (skip[i * 5 + j]) continue;
vis[i][j] = 0;
if (q.empty()) {
q.push({i, j});
vis[i][j] = 1;
}
}
}
}
bool check_around() {
init_vis();
int cnt = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
++cnt;
for (int d = 0; d < 4; ++d) {
int nx = x + dx[d], ny = y + dy[d];
if (nx < 0 || nx >= 5 || ny < 0 || ny >= 5) continue;
if (skip[nx * 5 + ny] || vis[nx][ny]) continue;
q.push({nx, ny});
vis[nx][ny] = 1;
}
}
return cnt == 7;
}
bool check_count() {
int cnt = 0;
for (int i = 0; i < 25; ++i) {
if (skip[i]) continue;
if (student[i / 5][i % 5] == 'S') ++cnt;
}
return cnt >= 4;
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) cin >> student[i][j];
}
for (int i = 7; i < 25; ++i) skip[i] = 1;
do {
if (!check_count()) continue;
if (!check_around()) continue;
++ans;
} while (next_permutation(skip, skip + 25));
cout << ans;
}
// 25C7 을 하고 과반인지, 인접했는지 확인
#include <bits/stdc++.h>
using namespace std;
char student[5][5];
int gang[7], board[5][5], ans;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, -1, 0, 1};
bool bfs_check(int r, int c) {
queue<pair<int, int> > q;
bool vis[5][5] = {};
q.push({r, c});
vis[r][c] = 1;
int cnt = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
++cnt;
for (int d = 0; d < 4; ++d) {
int nx = x + dx[d], ny = y + dy[d];
if (nx < 0 || nx >= 5 || ny < 0 || ny >= 5) continue;
if (!board[nx][ny] || vis[nx][ny]) continue;
q.push({nx, ny});
vis[nx][ny] = 1;
}
}
return cnt == 7;
}
void dfs(int k, int st, int soms) {
if (k == 7) {
if (soms >= 4 && bfs_check(gang[0] / 5, gang[0] % 5)) ++ans;
return;
}
for (int i = st; i < 25; ++i) {
gang[k] = i;
board[i / 5][i % 5] = 1;
// soms += 1 하지 않게 조심
dfs(k + 1, i + 1, soms + (student[i / 5][i % 5] == 'S'));
board[i / 5][i % 5] = 0;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) cin >> student[i][j];
}
dfs(0, 0, 0);
cout << ans;
}
if (student[i / 5][i % 5] == 'S') soms += 1;
을 하고
dfs(k+1, i+1, soms); 를 해서 틀렸었음.
soms += 1을 해버리면 내가 쓰이지 않는 다음 조합에도 영향을 미쳐버림.
soms값을 업데이트 하지 말고
dfs( , , soms + (student[i / 5][i % 5] == 'S'))를 해줘야 함.
그리고 vis 배열 지역변수로 하면 매번 초기화할 필요 없음.
좋은 질문이에요. 이건 알고리즘 실력과 문제 경험 모두가 필요한 감각적인 부분인데, 체계적으로 연습하면 충분히 길러질 수 있어요. 아래에 당신이 궁금해하는 핵심들을 단계별로 설명해볼게요.
이 문제는 다음과 같은 단서를 가지고 있어요:
조건 | 힌트가 되는 이유 |
---|---|
5x5 보드에서 7명을 선택한다 | → 25명 중 7명 선택: 조합 (25C7) |
인접해야 한다 (상하좌우) | → 선택 후 BFS/DFS로 연결성 검사 |
S가 최소 4명 이상 | → 선택한 7명 중 조건 필터링 |
“선택된 7명을 기준으로 조건을 검사한다” → 그러면 일단 7명을 모두 선택해보고 → 그때마다 조건을 검사하면 되겠다
이 패턴은 꽤 자주 나옵니다.
25C7 = 약 480,000
→ 각 조합마다 조건 확인 (BFS: O(7)) → 총 480,000 * 7 = 약 300만 연산
상황 | 전략 |
---|---|
제한된 크기 (예: 5x5, 최대 25개 등) | 완전탐색, 조합, 백트래킹을 적극 의심 |
전체 경우의 수가 100만 이하 | 나이브 가능성 높음 |
선택 후 조건 검사 (연결, 비율 등) | 조합 후 BFS/DFS로 조건 체크 |
여러 조건이 겹칠 경우 | "선택 먼저, 조건 나중" 패턴을 적용 |
NCr
은 N^r
보다 빠르게 줄어듦질문 | 답변 요약 |
---|---|
어떻게 이런 문제는 완전탐색임을 알 수 있을까? | 입력 크기 작음(5x5), 조건 검사 필요(S 개수, 인접성) → 조합 후 검사 전략 |
어떻게 눈치챌 수 있을까? | 전체 경우 수(25C7) 계산 → 시간 내 가능 → 나이브 가능성 판단 |
반복적으로 연습할 수 있는 기준은? | "작은 크기 + 조합/연결 조건 → 조합+검사 패턴"을 암기 |
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
int n, vs[10], ans;
pair<int, int> arr[10];
int break_eggs() {
pair<int, int> eggs[10];
for (int i = 0; i < n; ++i) eggs[i] = arr[i];
for (int i = 0; i < n; ++i) {
if (eggs[i].X <= 0 || eggs[vs[i]].X <= 0) continue;
eggs[i].X -= eggs[vs[i]].Y;
eggs[vs[i]].X -= eggs[i].Y;
}
int cnt = 0;
for (int i = 0; i < n; ++i) {
if (eggs[i].X <= 0) ++cnt;
}
return cnt;
}
void dfs(int k) {
if (k == n) {
ans = max(ans, break_eggs());
return;
}
for (int i = 0; i < n; ++i) {
if (i == k) continue;
vs[k] = i;
dfs(k + 1);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 0; i < n; ++i) cin >> arr[i].X >> arr[i].Y;
dfs(0);
cout << ans;
}
완전탐색. 단순 순열로 풂. 나이브한 풀이.
통과이긴 하다. 근데 이렇게 풀면 안 될 듯ㅠ
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
int n, ans, cnt;
pair<int, int> arr[10];
void dfs(int k) {
if (k == n) {
ans = max(ans, cnt);
return;
}
for (int i = 0; i < n; ++i) {
if (i == k || arr[k].X <= 0 || arr[i].X <= 0) continue;
arr[k].X -= arr[i].Y;
arr[i].X -= arr[k].Y;
if (arr[k].X <= 0) ++cnt;
if (arr[i].X <= 0) ++cnt;
dfs(k + 1);
if (arr[k].X <= 0) --cnt;
if (arr[i].X <= 0) --cnt;
arr[k].X += arr[i].Y;
arr[i].X += arr[k].Y;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 0; i < n; ++i) cin >> arr[i].X >> arr[i].Y;
dfs(0);
cout << ans;
}
if (i == k || arr[k].X <= 0 || arr[i].X <= 0) continue;
이 조건 때문에 더이상 깰 계란이 없으면 dfs를 아예 안 들어가게 됨.
k번째 계란이 칠 수 있는 계란이 하나도 없을 때 →
현재 코드는 dfs(k+1)이 절대 호출되지 않음
→ 즉, 아무 것도 못 치는 상황을 처리하지 않음 → 오답.
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
int n, ans, cnt;
pair<int, int> arr[10];
void dfs(int k) {
if (k == n) {
ans = max(ans, cnt);
return;
}
if (arr[k].X <= 0 ||
cnt == n - 1) { // 내가 더이상 칠 수 없을 때는 그냥 지나감
dfs(k + 1);
return;
}
for (int i = 0; i < n; ++i) {
if (i == k || arr[i].X <= 0) continue;
arr[k].X -= arr[i].Y;
arr[i].X -= arr[k].Y;
if (arr[k].X <= 0) ++cnt;
if (arr[i].X <= 0) ++cnt;
dfs(k + 1);
if (arr[k].X <= 0) --cnt;
if (arr[i].X <= 0) --cnt;
arr[k].X += arr[i].Y;
arr[i].X += arr[k].Y;
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 0; i < n; ++i) cin >> arr[i].X >> arr[i].Y;
dfs(0);
cout << ans;
}
엉엉... 이렇게 풀어야 하는 거구나....
dfs에서 다시 빠질 때 원래 값을 돌려 놓으면
따로 배열을 쓸 필요가 없다.
그리고 내 계란으로 다른 걸 칠 수 없는 상황이라면 아무것도 안 하고 dfs 탐색을 더 들어가야 함.
만약 아래 for문에서 continue 조건문으로 걸어버렸다면 탐색을 더 들어가지 않고 그 자리에서 끝나버림. 이러면 문제.
그리고 나 빼고 다 깨진 상황에서도 k == n return;을 만나기 위해선 탐색을 더 들어가줘야 함.
그래서
if (arr[k].X <= 0 ||
cnt == n - 1) { // 내가 더이상 칠 수 없을 때는 그냥 지나감
dfs(k + 1);
return;
}
조건이 꼭 필요.
정말 좋은 질문이에요.
그리고 이건 단순히 "어떻게 코드를 짜야 하나"가 아니라,
"문제를 보고 어떻게 사고를 전개해야 하나",
**"어떤 흐름으로 브루트포스를 만들 수 있나"**를 묻는 진짜 본질적인 질문이에요.
그걸 지금 질문하고 있다는 것만으로도,
당신은 문제 풀이력의 핵심을 건드리고 있어요.
아래는 이 문제를 처음부터 설계해가는 사고 흐름을 단계적으로 정리한 거예요.
한 줄씩 따라가면서 스스로도 떠올려보세요.
계란을 차례대로 들고, 아직 깨지지 않았으면 다른 계란 하나를 골라 쳐본다.
양쪽 계란 모두 내구도가 깎이고, 0 이하가 되면 깨진다.
가장 많은 계란이 깨진 경우를 찾아라.
→ "차례로 든다", "안 깨졌으면 친다", "서로 내구도 감소", "되돌리기 가능"
→ 이걸 보고 떠오를 수 있는 대표적인 키워드가 있어요:
"백트래킹 + 시뮬레이션"
문제가 "모든 경우를 시도"해야 하므로 DFS를 떠올리고:
void dfs(int k) {
if (k == n) {
// 종료 조건
return;
}
// k번째 계란을 들어서 뭘 한다
}
이건 문제 조건 그대로예요:
if (arr[k].X <= 0 || cnt == n - 1) {
dfs(k + 1); // 그냥 넘기기
return;
}
여기서 cnt == n - 1
은
→ 나 빼고 다 깨져 있으면 칠 계란이 없음
이걸 따로 생각하지 않고, for
문 돌면서 "칠 애가 없으면 넘긴다"는 구조도 가능해요 (후술)
for (int i = 0; i < n; ++i) {
if (i == k || arr[i].X <= 0) continue;
// 시뮬레이션
arr[k].X -= arr[i].Y;
arr[i].X -= arr[k].Y;
// 깨졌는지 확인
if (arr[k].X <= 0) cnt++;
if (arr[i].X <= 0) cnt++;
dfs(k + 1);
// 백트래킹 (상태 복원)
if (arr[k].X <= 0) cnt--;
if (arr[i].X <= 0) cnt--;
arr[k].X += arr[i].Y;
arr[i].X += arr[k].Y;
}
이건 보통 bool hit = false;
같은 플래그를 써서 해결:
bool hit = false;
for (int i = 0; i < n; ++i) {
// ...
hit = true;
// ...
}
if (!hit) dfs(k + 1); // 칠 수 있는 계란이 없으면 그냥 넘긴다
k == n
: 다 들었으니 종료
arr[k]
깨졌거나 칠 애가 없으면 넘김 (dfs(k + 1)
)
안 깨졌으면 for
로 모든 살아 있는 계란을 돌며
치고 싶은 애가 하나도 없으면 넘김
문제 조건을 말로 다 써보는 습관
**“매 순간 가능한 행동이 무엇인가?”**를 생각해보기
그리고 반드시:
이건 한두 문제 푼다고 머릿속에 딱 생기는 게 아니라,
문제 하나 풀 때마다 그 흐름을 내 것으로 만들려는 태도에서 생겨요.
그리고 지금 당신은 정확히 그걸 하고 있어요.
ㅠㅠ...
#include <bits/stdc++.h>
using namespace std;
int n, m, g, r, y, ans;
int board[52][52], yellow[2510], gr[50];
bool gused[50];
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, -1, 0, 1};
void bfs() {
queue<tuple<int, int, char> > q;
int dist[52][52] = {};
char type[52][52] = {};
for (int i = 0; i < g + r; ++i) {
int x = gr[i] / m, y = gr[i] % m;
char color = gused[i] ? 'G' : 'R';
q.push({x, y, color});
dist[x][y] = 1;
type[x][y] = color;
}
int flower = 0;
while (!q.empty()) {
auto [x, y, c] = q.front();
q.pop();
if (type[x][y] == 'F') continue; // 꽃 피웠으면 퍼지지 않음
for (int d = 0; d < 4; ++d) {
int nx = x + dx[d], ny = y + dy[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (board[nx][ny] == 0) continue;
if (dist[nx][ny] == 0) {
// 아직 방문 안 했으면 퍼짐
dist[nx][ny] = dist[x][y] + 1;
type[nx][ny] = c;
q.push({nx, ny, c});
continue;
}
if (dist[nx][ny] == dist[x][y] + 1 && type[nx][ny] != c &&
type[nx][ny] != 'F') {
// 같은 시간에 다른 색 -> 꽃 생성
type[nx][ny] = 'F';
flower++;
}
}
}
ans = max(ans, flower);
}
void grCg(int k, int st) {
if (k == g) {
bfs();
return;
}
for (int i = st; i < g + r; ++i) {
gused[i] = 1;
grCg(k + 1, i + 1);
gused[i] = 0;
}
}
void yCgr(int k, int st) {
if (k == g + r) {
grCg(0, 0);
return;
}
for (int i = st; i < y; ++i) {
gr[k] = yellow[i];
yCgr(k + 1, i + 1);
}
}
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m >> g >> r;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> board[i][j];
if (board[i][j] == 2) yellow[y++] = i * m + j;
}
}
yCgr(0, 0);
cout << ans;
}
// yellow 중 g+r 만큼 뽑고
// g+r 중 g 만큼 뽑음
// bfs로 검사
못해먹겠다....
황토색 토양 중 g+r만큼 뽑고 (yCgr)
그 중 g만큼 또 뽑으면 (grCg) 각 배양액의 위치 조합을 구하는 것까진 OK.
여기까진 잘 풀었다.
그 다음 bfs가 너무 까다로웠음......
이 문제의 경우 아직 방문 하지 않은 경우 q에 push하고 퍼져나가게 해야한다. 가 명확하기 때문에
if (dist[nx][ny] == 0) {
// 아직 방문 안 했으면 퍼짐
dist[nx][ny] = dist[x][y] + 1;
type[nx][ny] = c;
q.push({nx, ny, c});
continue;
}
먼저 적어주면 좋음.
다른 bfs에서는 건너뛰어야 할 조건들을 if문을 먼저 적고 continue;를 해준 다음 q.push()를 했어서 여기도 그런 구조로 하려고 했다가 괜히 더 헷갈렸음.
다시 정리
- OOB면 continue
- 호수면 continue
- 아직 방문 안 한 경우만 bfs. 그리고 continue
- 만약 시간이 같은데 다른 배양액 만난 경우 개화
복잡할 땐 확실한 조건들을 하나하나 적고 continue 하기