포스트

1062 가르침

문자열 완전탐색 문제

DFS를 이용한 완전 탐색을 이용해 문제를 풀이한다

https://www.acmicpc.net/problem/1062

1. 문제


간단 설명

N개의 단어가 주어진다

K개의 알파벳을 사용할 수 있음

각 단어에서 모르는 알파벳이 있다면, 그 단어는 읽을 수 없음

즉, 한 문자열에서 모든 알파벳을 배운 적 있어야, 그 문자열을 읽을 수 있다!

2. 문제 분석


필요변수

단어 개수 N, 배울 글자 수 K

N개의 단어를 저장할 벡터 words

배운 알파벳들을 저장할 path

주의점

  • 모르는 알파벳이 나왔다고 바로바로 학습해버리면 최대값을 구하지 못함
  • 시간초과에 주의할 것
  • 접두사 anta와 접미사 tica는 무조건 붙는다

알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. N과 K를 입력받는다
	- K가 5보다 작다면 anta와 tica를 배우기에도 부족하므로 모든 단어를 읽을 수 없음. 0 리턴
	- K가 26이라면 모든 단어를 읽을수 있으므로 단어의 개수 N을 리턴
	
2. a, n, t, i, c 단어는 기본적으로 배운것으로 고려한다
	- 모든 단어에 접두사, 접미사로 들어감
	- 알파벳을 배웠다면 alpha 테이블에 표시해둔다 

3. DFS 조합을 이용해 나머지 배울 단어들을 고른다
	// DFS 탈출 조건
	3-1. 레벨이 K-5보다 크다면(이미 antic 알파벳 5개를 배웠으므로) 
		현재 생성한 path 문자열로 단어장에서 몇개를 읽을 수 있는지 확인한다
		3-1-1. 단어장의 각 단어를 앞 글자부터 끝까지 확인한다
		3-1-2. 만약 표기해둔 alpha 테이블에 0으로 표시된 안배운 단어가 있다면 못읽는 단어이므로 패스.
		3-1-3. 끝까지 읽을 수 있었다면 카운트 1 증가 시킨다
	
	// DFS 더 들어가는 부분
	3-2. 레벨이 K-5 보다 작다면 배울 알파벳을 골라 `path` 문자열에 추가한다
		3-2-1. 이미 배운 알파벳이라면 패스한다
		3-2-2. 알파벳을 배웠다면 alpha 테이블에 표시해둔다 
		3-2-3. DFS 조합을 다시 호출하여 레벨을 1 증가시키고, start부터 시작한다
		3-2-4. DFS를 나왔으므로 다시 초기화 해준다

Direct Access Table을 이용해 알파벳을 배웠는지 여부를 표시해두고,

각 문자열에서 알파벳이 나올때마다 바로 참조한다

만약 배운적 없는 알파벳이라면 못읽는 단어이므로 패스

(이전에 시도했던 문자열에서 알파벳을 find하고 erase 하는 방법보다 훨씬 빠름)

1. 처음 시도한 접근법

1
2
3
1. 학습할 수 있는 단어의 개수가 5개 미안이라면, anta, tica도 못읽으므로 0개 리턴
2. 단어장의 첫 단어의 첫 알파벳을 고르고, 모든 단어에서 이 알파벳을 지운다 (학습해서 읽을 수 있음)
4. 단어가 빈 문자열이 된다면 지우고 답을 1 더해준다(읽을수 있다)

그러나 이 방법은 첫 단어가 어떤 단어냐에따라 최적의 해를 구할 수 없었음

  • 예제 3번의 테케 통과 못함

2. 두번째 시도한 접근법

1
2
3
DFS를 통해 배운 알파벳들로 조합을 만들고
조합으로 생성한 알파벳 각각을 단어에서 지워나간다
단어가 빈 문자열이 된다면 읽을 수 있음

세번째 테케는 통과했으나 시간초과…

DFS와 find, erase 탐색에 시간이 오래 걸린듯

3. 소스코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 정답 코드. 
#include <iostream>
#include <string>
#include <vector>


using namespace std;

int N, K;
int alpha[26]; // 배운 알파벳을 저장하는 배열
int answer = 0;
string path;
vector<string> words;

// 현재 생성한 path 문자열로 word에 몇개를 읽을 수 있는지 
int CheckWord(string path) {
	bool flag;
	int cnt = 0;
	// 이 문자가 배운적이 없는 문자인지를 체크

	for (string str : words) {
		flag = true;
		
		for (char ch : str) {
			// 문자열을 하나씩 지울 필요 없이 Direct Index Table 에서 배운 단어인지를 바로 확인한다
			if (alpha[(int)(ch - 'a')] == 0) {
				flag = false;
				break;
			}
		}
		if (flag == true) {
			cnt++;
		}
	}

	return cnt;
}

void DFS(int level, int start, string path) {
	if (level == K - 5) {
		answer = max(answer, CheckWord(path));
		return;
	}

	for (int i = start; i < 26; i++) {
		if (alpha[i] == 1) continue; // 이미 배운 단어
		
		char ch = (char)('a' + i);
		alpha[i] = 1; // 배웠음
		path.push_back((char)('a' + i));

		DFS(level + 1, i, path);

		alpha[i] = 0;
		path.pop_back();
	}

}

int main() {
	freopen_s(new FILE*, "input.txt", "r", stdin);
	cin >> N >> K;

	if (K < 5) {
		cout << 0;
		return 0;
	}
	else if (K == 26) {
		cout << N;
		return 0;
	}

	for (int i = 0; i < N; i++) {
		string tmp;
		cin >> tmp;
		words.push_back(tmp);
	}

	// anta tica 를 읽기 위한 기본 5개 알파벳
	alpha[(int)('a' - 'a')] = 1;
	alpha[(int)('n' - 'a')] = 1;
	alpha[(int)('t' - 'a')] = 1;
	alpha[(int)('i' - 'a')] = 1;
	alpha[(int)('c' - 'a')] = 1;

	DFS(0, 0, "");
	cout << answer;

	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 처음 시도한 접근법

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int N, K;
vector<string> words;
int answer = 0;

int main() {
	// input
	freopen_s(new FILE*, "input.txt", "r", stdin);
	cin >> N >> K;

	for (int i = 0; i < N; i++) {
		string tmp;
		cin >> tmp;
		words.push_back(tmp);
	}

	// 단어가 5개 이하라면, anta, tica의 a n t i c 도 못읽으므로 0개 
	if (K < 5) {
		cout << 0;
		return 1;
	}
		
	while (K--) {
		if (words.empty()) break; // 알파벳은 더 배울수 있는데 더이상 단어가 없는경우

		// 첫 원소의 첫 알파벳을 학습한다.
		char ch = words[0][0];

		// 학습해서 읽을 수 있으므로 
		// 모든 단어에서 이 알파벳을 모두 제거한다		

		for (string& str : words) {
			while (str.find(ch) != string::npos)
			{
				str.erase(str.find(ch), 1);
			}

			for (auto it = words.begin(); it != words.end();) {
				if (*it == "") {
					it = words.erase(it);
					answer++;
				}
				else {
					++it;
				}
			}
		}		
	}

	cout << answer;

	return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// 두번째 시도한 접근법

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int N, K;
vector<string> words;
int alpha[26];
int answer = 0;

// 배운 알파벳들의 리스트 path로, str을 만들수 있는지 체크 
// path에는 조합으로 생성한 알파벳들의 리스트가
// str에는 타겟 문자열이 들어있다
bool check_words(string path, string word) {
	for (int i = 0; i < 26; i++) {  // alpha도 포함해야 하므로 
		if (alpha[i] == 0) continue; // 안배운 단어
		
		char ch = (char)('a' + i);

		while (word.find(ch) != string::npos) {
			word.erase(word.find(ch), 1);
		}
		
		// 모두 지울 수 있었다면 OK
		if (word == "") {
			return true;
		}
	}
	return false;

}

void DFS(int level, int start, string path) {
	if (level == K - 5) {
		int cnt = 0;

		// 몇개를 만들수 있는지 체크한다
		for (string str : words) {
			if (check_words(path, str)) {
				cnt++;
			}
		}
		
		// 최대값과 비교후 갱신
		answer = max(answer, cnt);

		return;
	}

	// 알파벳을 하나씩 배우는 조합
	for (int i = start; i < 26; i++) {
		if (alpha[i] == 1) continue; // 이미 배웠다면 패스

		alpha[i] = 1;
		path.push_back('a' + i);

		DFS(level+1, i, path);

		path.pop_back();
		alpha[i] = 0;
	}
}

int main() {
	freopen_s(new FILE*, "input.txt", "r", stdin);
	cin >> N >> K;

	// anta tica를 못읽음
	if (K < 5) {
		cout << 0;
		return 1;
	}
	// 모든 단어를 배워 모두 읽을 수 있음
	else if(K == 26){
		cout << N;
		return 2;
	}

	for (int i = 0; i < N; i++) {
		string tmp;
		cin >> tmp;
		words.push_back(tmp.substr(4,tmp.length() - 8));
	}

	// antica는 기본으로 배운다
	alpha[(int)('a' - 'a')] = 1;
	alpha[(int)('n' - 'a')] = 1;
	alpha[(int)('t' - 'a')] = 1;
	alpha[(int)('i' - 'a')] = 1;
	alpha[(int)('c' - 'a')] = 1;
	
	DFS(0, 0, "");

	cout << answer;

	return 0;
}

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.