diff --git a/pps/b1034.md b/pps/b1034.md index 20daf0e..c56a1e0 100644 --- a/pps/b1034.md +++ b/pps/b1034.md @@ -51,9 +51,13 @@ parent: 문제 풀이
문제에 따르면 특정 열의 스위치를 누르면 해당 열의 램프의 전원이 반대가 되고, 우리는 K번 스위치를 눌러 켜진 행이 최대가 되는 시점을 찾아야 한다. 켜져있는 행이 있을 때 어느 스위치를 누르던 켜져있던 행이 사라지므로, 켜진 행을 하나 완성했을 때 동시에 켜진 행의 개수가 최대인 시점을 찾아야 한다. -임의의 두 행에 대하여 하나의 행이 켜질 때 다른 행도 켜지려면, 스위치를 누르기 전에 이미 켜져있던 램프의 위치와 개수가 서로 같아야 하므로 두 행은 완전히 동일해야 한다. +임의의 두 행에 대하여 **하나의 행이 켜질 때 다른 행도 켜지려면**, +스위치를 누르기 전에 이미 켜져있던 램프의 위치와 개수가 서로 같아야 하므로 **두 행은 완전히 동일**해야 한다. 결론적으로, 하나의 행에 대해 완전히 동일한 행의 개수가 곧 동시에 켜지는 행이므로, 임의의 행과 동일한 행의 개수가 최대가 되는 시점이 켜질 수 있는 행의 최댓값이다. +{: .mb-1 } + 때문에, 본 문제는 애드 혹(ad hoc) 문제로 분류되었다. +{: .mt-1 } {: .note-title } > 애드 혹(ad hoc) 문제란? @@ -84,20 +88,28 @@ int main() { int maxCount = 0; for(int i = 0; i < n; i++) { + // 꺼진 램프를 켜기 위해 켜야 하는 스위치 개수 int switchCount = 0; + // 자신과 똑같은 행의 개수 + int count = 0; + for(int j = 0; j < m; j++) { if(lamp[i][j] == '0') switchCount++; } - int count = 0; + // 스위치를 누를 횟수가 켜야 하는 스위치 개수 이상이어야 하며, + // 해당 행을 켰을 때 남은 눌러야 하는 스위치 개수가 짝수이어야만 + // 해당 행을 켤 수 있다. (짝수 번이면 특정 스위치를 두 번 눌러 횟수만 소진 가능) if(switchCount <= k && (k - switchCount) % 2 == 0) { + // 켜질 수 있는 행과 동일한 행의 개수 확인 for(int j = 0; j < n; j++) { if(lamp[i].compare(lamp[j]) == 0) count++; } } + // 최댓값 비교 if(maxCount < count) maxCount = count; } diff --git a/pps/b1188.md b/pps/b1188.md index 86433f7..34eba7e 100644 --- a/pps/b1188.md +++ b/pps/b1188.md @@ -49,17 +49,35 @@ parent: 문제 풀이 {: .fw-700 }
-문제에 따르면 특정 열의 스위치를 누르면 해당 열의 램프의 전원이 반대가 되고, 우리는 K번 스위치를 눌러 켜진 행이 최대가 되는 시점을 찾아야 한다. -켜져있는 행이 있을 때 어느 스위치를 누르던 켜져있던 행이 사라지므로, 켜진 행을 하나 완성했을 때 동시에 켜진 행의 개수가 최대인 시점을 찾아야 한다. -임의의 두 행에 대하여 하나의 행이 켜질 때 다른 행도 켜지려면, 스위치를 누르기 전에 이미 켜져있던 램프의 위치와 개수가 서로 같아야 하므로 두 행은 완전히 동일해야 한다. -결론적으로, 하나의 행에 대해 완전히 동일한 행의 개수가 곧 동시에 켜지는 행이므로, 임의의 행과 동일한 행의 개수가 최대가 되는 시점이 켜질 수 있는 행의 최댓값이다. -때문에, 본 문제는 애드 혹(ad hoc) 문제로 분류되었다. +이번 문제는 n개의 소세지를 동일한 비율로 m명에게 나누기 위해 소세지를 자르는 최소 횟수를 구하는 문제이다. +{: .mb-1 } + + +기초부터 풀어나가면, n < m을 만족하는 서로소인 n, m에 대해 n/m만큼 쪼개기 위해서는 반드시 최소 m-1번의 칼질을 해야 한다. +이는 소세지 여러 개를 하나의 소세지라고 생각하면 **반드시 m-1번의 칼질**이 필요하다는 것을 알 수 있다. +{: .my-1 } + + +원래는 하나의 소세지가 아니기 때문에, 칼질을 하려는 부분이 각 소세지의 끝과 끝이 맞닿아 있는 부분이라면 칼질을 하지 않아도 된다. +n개의 소세지를 자르는 중 소세지 끝이 이미 나뉘어 칼질을 하지 않기 위해서는 n과 m에 1이 아닌 공약수가 있어야 하며, +공약수가 있다는 것은 현재 소세지를 공약수만큼 소분화할 수 있다는 것이다. +이때, n과 m의 공약수만큼 소분화했을 때 나눈 각 동일한 집합들은 서로소일 때 하나의 소세지처럼 간주할 수 있으므로, +**n과 m을 가장 작은 자연수의 비로 나타냈을 때의 결과를 n과 m의 공약수만큼 반복**하면 된다. +이렇게 소세지를 잘랐을 때 소세지 하나의 크기는 n/m으로, 공약수에 관계 없이 항상 일정하기 때문에 하나의 소세지를 자르는 횟수를 반복하는 것과 같다. +{: .my-1 } + +결론적으로, n과 m이 서로소일 때와 서로소가 아닐 때를 각각 구분지어 문제를 해결하면 된다. +n과 m이 서로소일 경우, 하나의 소세지를 자르는 것처럼 간주하며 m-1번의 칼질이 최소 횟수가 된다. +반대로 n과 m이 서로소가 아닌 경우, 서로소일 떄의 소세지 개수가 공약수만큼 늘어난 것과 같으므로 +가장 작은 자연수의 비일때의 자르는 횟수를 최대공약수만큼 반복해주면 된다. +{: .mt-1 } {: .note-title } -> 애드 혹(ad hoc) 문제란? +> 서로소(coprime)란? > -> 애드 혹 문제는 그 어원처럼 일반화되지 않은 단순히 현재 문제만을 위한 풀이를 요구한다. -> 이 문제의 경우에는 브루트포스나 DP처럼 각 경우를 탐색하여 찾는 대신 편법과도 같은 특정 풀이가 존재하기에 애드 혹 문제로 분류된다. +> 두 정수 a, b에 대해 최대공약수가 1이라면, 두 정수 a와 b는 서로소(coprime)라고 말한다. +> 서로소인 두 정수는 그 자체로 가장 작은 정수의 비로 나타나 있으며, 최대공배수가 서로의 곱이다. +> 두 정수가 소수(prime)인가와 서로소인가는 서로 독립사건이며, 두 정수가 모두 짝수이면 반드시 서로소가 아니다.
## 풀이 코드 @@ -70,7 +88,7 @@ parent: 문제 풀이 using namespace std; -// 유클리드 호제법 +// 유클리드 호제법(최대공약수 반환) int gcd(int a, int b) { if(b == 0) return a; return gcd(b, a % b); @@ -80,14 +98,11 @@ int main() { int n, m; cin >> n >> m; - // n < m을 만족하는 서로소인 n, m에 대해 n/m만큼 쪼개기 위해서는 반드시 최소 m-1번의 칼질을 해야한다. - // 이는 소세지 여러 개를 하나의 소세지라고 생각하면 반드시 m-1번의 칼질이 필요하다는 것을 알 수 있다. - // n개의 소세지를 자르는 중 소세지 끝이 이미 나뉘어 칼질을 하지 않기 위해서는 n과 m이 서로소의 배수여야 한다. - // 다시 말해, 이미 서로소로 하나의 소세지처럼 잘린 만큼이 n과 m의 공약수만큼 반복되고 있다는 것이다. - // 결론적으로 n과 m이 서로소가 아닌 경우, 쪼개는 비율인 n/m은 약분에 따라 서로소일 떄의 칼질 횟수와 같으며 - // 서로소 대비 n과 m의 최대공약수만큼 소세지의 개수가 배가 되므로, 위의 횟수에 최대공약수 배만큼의 칼질을 더해줘야 한다. + // n과 m의 최대공약수 계산 int div = gcd(n % m, m); + // n과 m의 가장 작은 자연수의 비 m /= div; + // m-1에 최대공약수 곱셈 cout << div * (m - 1) << endl; return 0; }