문제 출처 : https://www.acmicpc.net/problem/2224
문제
수학, 혹은 논리학에서 만약 무엇 이라면 뭣 일 것이다 하는 식의 명제가 널리 쓰인다. 예를 들어 "P이면 Q일 것이다." 라는 명제는 “P => Q” 라는 기호로 표현된다. 이때의 P를 전건, Q를 후건이라고 한다.
논리학에서 어떤 명제를 증명할 때 가장 널리 쓰이는 방법 중 한 가지가 바로 삼단 논법이다. 만약 두 명제 “P => Q", "Q => R" 가 모두 참이면, 명제 "P => R"이 역시 참이 된다. 이러한 방법을 사용했을 때 명제 ”P => R"이 증명되었다고 한다.
어떤 참인 명제가 주어졌을 때, 이 명제가 참이므로 이 명제 자체도 증명될 수 있다고 할 수 있다. 하지만 “P => P”와 같은 명제는 항상 참이 되는데, 이런 식으로 전건과 후건이 같은 경우는 출력하지 않기로 한다.
N개의 참인 명제들이 주어졌을 때, 증명될 수 있는 명제를 모두 구해내는 프로그램을 작성하시오. 명제를 증명하는 방법은 여러 가지가 있을 수 있지만, 위에서 언급한 방법만을 사용하기로 한다.
입력
첫째 줄에 정수 N(1 ≤ N ≤ 10,000)이 주어진다. 다음 N개의 줄에는 참인 명제들이 주어진다. 명제는 "P => Q"의 꼴로 주어지는데, “=>”는 앞뒤가 공백으로 구분되어 있다. P나 Q는 명제를 나타내는 문자인데, 알파벳 대소문자 한 글자를 사용한다. 같은 명제가 여러 번 주어질 수도 있다.
출력
첫째 줄에 출력할 명제의 개수 X개를 출력한다. 다음 X개의 줄에 증명될 수 있는 명제를 한 줄에 하나씩 출력한다. 명제를 출력할 때에는 전건 순으로 정렬하고, 전건이 같은 경우에는 후건 순으로 정렬한다. 알파벳은 대문자가 소문자에 우선한다. 즉, 정렬했을 때 A, B, …, Z, a, b, …, z 순서로 나와야 한다.
알고리즘 분류
풀이
n개의 간선이 주어질 때, n 개의 간선의 전건에서 이어질 수 있는 모든 후건에 대해 탐색해야 한다.
즉, 모든 정점에서 모든 정점에 갈 수 있는지 없는지 확인하면 된다. 고로 플로이드 와샬 알고리즘을 사용한다.
n의 크기가 10000이라서 3중 포문을 사용하는 플로이드 와샬 알고리즘을 사용하면 시간 초과가 나는 거 아니야?
라고 생각할 수 있는데, 여기서 n은 간선의 개수를 뜻하며, 실제 플로이드 와샬이 돌아야 할 구간은 각 포문마다 A~z의 개수만큼이다.
본인은 명제를 확인하기 위한 2차원 배열을 bool dp[58][58]로 선언했다.
z의 아스키 코드 값은 122이고, A의 아스키 코드 값은 65이기 때문에, A의 인덱스를 0, z의 인덱스를 57로 사용하였다.
실제 A~z의 개수는 52개이지만, Z 와 a 사이에 6개의 다른 문자가 끼어있기 때문에 위와 같이 배열을 잡았다.
주의할 점은 입력에서 같은 명제가 나올 수 있다는 점, 전건과 후건이 같을 수 있다는 점이다.
나머진 코드를 보면 단번에 이해할 수 있을 것이다.
코드
#include <iostream>
#include <string>
using namespace std;
int n;
//1<=n<=10000
bool dp[58][58];
int cnt = 0;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
cin.ignore();
for (int i = 0; i < n; i++) {
string input;
getline(cin, input);
//a=>a인 경우 skip
if (input[0] == input[input.length() - 1]) continue;
//중복입력인 경우 skip
if (dp[input[0] - 'A'][input[input.length() - 1] - 'A']) continue;
cnt++;
dp[input[0] - 'A'][input[input.length() - 1] - 'A'] = true;
}
//
for (int k = 0; k < 58; k++) {
for (int i = 0; i < 58; i++) {
for (int j = 0; j < 58; j++) {
//이미 참이라고 밝혀진 경우 스킵
if (dp[i][j] || i==j)continue;
//i=>k 이고, k=>j 이면 i=>j
dp[i][j] = dp[i][k] && dp[k][j];
if (dp[i][j])
cnt++;
}
}
}
cout << cnt << '\n';
for (int i = 0; i < 58; i++) {
for (int j = 0; j < 58; j++) {
if (dp[i][j]) {
cout << (char)(i + 'A') << " => " << (char)(j + 'A')<<'\n';
}
}
}
return 0;
}
'알고리즘 문제 풀이 > 백준' 카테고리의 다른 글
백준 1548 부분 삼각 수열 c++ (완전탐색) (0) | 2021.11.16 |
---|---|
백준 16926 배열 돌리기 1 c++ (구현) (0) | 2021.11.15 |
백준 11404 플로이드 c++ (플로이드 와샬) (0) | 2021.11.13 |
백준 15486 퇴사 2 c++ (dp) (0) | 2021.11.12 |
백준 13305 주유소 c++ (탐욕법) (0) | 2021.11.11 |
댓글