백준 문제/백트래킹

[백준] 1182번 부분수열의 합

dubu0721 2025. 1. 27. 11:17

문제: 1182번: 부분수열의 합

basic-algo-lecture/workbook/0x0C.md at master · encrypted-def/basic-algo-lecture

 

basic-algo-lecture/workbook/0x0C.md at master · encrypted-def/basic-algo-lecture

바킹독의 실전 알고리즘 강의 자료. Contribute to encrypted-def/basic-algo-lecture development by creating an account on GitHub.

github.com

 

  • 틀린 코드..
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <iomanip> // setprecision을 사용하기 위한 헤더
#include <climits>
#include <list>

using namespace std;

int n, s;
int nums[20];
int visits1[1000001]; // 음수 방문 표시(절댓값)
int visits2[1000001]; // 양수 방문 표시
int sum = 0; // 재귀 돌면서 값을 뺐다 더했다..
int cnt = 0;

void Funct(int functN) {
	// 부분 수열의 합이 s 와 같으면 cnt 값 +1 하기(그리고 공집합이 아니어야 함)
	if (sum == s && functN != 0) {
		cnt++;
		return;
	}

	// 현재 부분수열의 크기가 5인데 위 조건문에 걸러지지 않았다면 그냥 빠져나가기..
	if (functN == n) {
		return;
	}

	// nums 를 돌면서 하나씩 더해줄것..
	for (int i = 0; i < n; i++) {
		// 방문 표시 확인(이미 방문했으면 그냥 지나가가ㅣ..)
		if (nums[i] < 0) {
			if (visits1[-nums[i]] == 1) {
				continue; 
			}
		}
		else {
			if (visits2[nums[i]] == 1) { 
				continue; 
			}
		}

		// 방문..
		if (nums[i] < 0) {
			visits1[-nums[i]] = 1; // 방문 표시
			sum += nums[i];

			Funct(functN + 1); // 재귀 돌리기

			visits1[-nums[i]] = 0; // 방문 표시 없애기
			sum -= nums[i];
		}
		else if (nums[i] > 0) {
			visits2[nums[i]] = 1; // 방문 표시
			sum += nums[i];

			Funct(functN + 1); // 재귀 돌리기

			visits2[nums[i]] = 0; // 방문 표시 지우기
			sum -= nums[i];
		}
		else {
			// 0 방문 표시
			visits1[0] = 1;
			visits2[0] = 1;

			Funct(functN + 1); // 재귀 돌리기

			// 0 방문 표시 지우기..
			visits1[0] = 0;
			visits2[0] = 0;
		}
	}
}

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	cin >> n >> s;
	for (int i = 0; i < n; i++) {
		int tmp;
		cin >> tmp;
		nums[i] = tmp;
	}
	Funct(0); // 일단 0으로 시작..
	cout << cnt;

	return 0;
}

 

이 풀이의 문제점은 중복된 수열까지 cnt 에 더해버리는 것..

 

 

  • 정답 코드
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <iomanip> // setprecision을 사용하기 위한 헤더
#include <climits>
#include <list>

using namespace std;

int n, s;
int nums[21];
int cnt = 0;

void Funct(int functN, int tmpSum) {
	if (functN == n) {
		if (tmpSum == s) {
			cnt++;
		}
		return;
	}

	// 재귀 호출할 때 현재 sum 값에 현재 값을 더한 경우와 안 더한 경우로 나눠서..
	Funct(functN + 1, tmpSum); // 안 더한 경우
	Funct(functN + 1, tmpSum + nums[functN]); // 더한 경우
}

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	cin >> n >> s;
	for (int i = 0; i < n; i++)
		cin >> nums[i];

	Funct(0, 0);

	if (s == 0)
		cnt--; // 공집합인 경우 빼야함..
	cout << cnt;

	return 0;
}

 

결국 강의 영상 보고 풀었는데 훨씬 쉽게 푸는 방법이 있었다..

 

다음 단계로 넘어가려고 할 때 현재 값을 더하는 경우와 안 더하는 경우 두 가지로 나눠서 재귀적으로 호출하면 되는 거였다;; 그러다가 현재 functN 값이 n 과 같아지면 sum 이 s 와 같은지 확인하고 같으면 cnt++ 해주면 되는 거였다..

 

더하는 경우와 안 더하는 경우로 나누는 방식을 앞으로도 기억하고 있어야 할 것 같다;;;

 

 

 

참고자료:

BaaaaaaaarkingDog | [실전 알고리즘] 0x0C강 - 백트래킹

 

[실전 알고리즘] 0x0C강 - 백트래킹

이번에는 백트래킹을 배워보도록 하겠습니다. 백트래킹도 재귀와 더불어 많은 사람들이 고통을 호소하는 알고리즘 중 하나이지만 의외로 그 재귀적인 구조에 매료되어서 참재미를 알아버리는

blog.encrypted.gg