Skip to content

Commit

Permalink
✍️ create: baekjoon 1188
Browse files Browse the repository at this point in the history
- 백준 1188번 문제풀이
  • Loading branch information
marunemo committed Dec 19, 2023
1 parent b1a6ef9 commit 03b6906
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 17 deletions.
16 changes: 14 additions & 2 deletions pps/b1034.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ parent: 문제 풀이
<div class="code-example" markdown="1">
문제에 따르면 특정 열의 스위치를 누르면 해당 열의 램프의 전원이 반대가 되고, 우리는 K번 스위치를 눌러 켜진 행이 최대가 되는 시점을 찾아야 한다.
켜져있는 행이 있을 때 어느 스위치를 누르던 켜져있던 행이 사라지므로, 켜진 행을 하나 완성했을 때 동시에 켜진 행의 개수가 최대인 시점을 찾아야 한다.
임의의 두 행에 대하여 하나의 행이 켜질 때 다른 행도 켜지려면, 스위치를 누르기 전에 이미 켜져있던 램프의 위치와 개수가 서로 같아야 하므로 두 행은 완전히 동일해야 한다.
임의의 두 행에 대하여 **하나의 행이 켜질 때 다른 행도 켜지려면**,
스위치를 누르기 전에 이미 켜져있던 램프의 위치와 개수가 서로 같아야 하므로 **두 행은 완전히 동일**해야 한다.
결론적으로, 하나의 행에 대해 완전히 동일한 행의 개수가 곧 동시에 켜지는 행이므로, 임의의 행과 동일한 행의 개수가 최대가 되는 시점이 켜질 수 있는 행의 최댓값이다.
{: .mb-1 }

때문에, 본 문제는 애드 혹(ad hoc) 문제로 분류되었다.
{: .mt-1 }

{: .note-title }
> 애드 혹(ad hoc) 문제란?
Expand Down Expand Up @@ -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;
}
Expand Down
45 changes: 30 additions & 15 deletions pps/b1188.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,35 @@ parent: 문제 풀이
{: .fw-700 }

<div class="code-example" markdown="1">
문제에 따르면 특정 열의 스위치를 누르면 해당 열의 램프의 전원이 반대가 되고, 우리는 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)인가와 서로소인가는 서로 독립사건이며, 두 정수가 모두 짝수이면 반드시 서로소가 아니다.
</div>
## 풀이 코드
Expand All @@ -70,7 +88,7 @@ parent: 문제 풀이

using namespace std;

// 유클리드 호제법
// 유클리드 호제법(최대공약수 반환)
int gcd(int a, int b) {
if(b == 0) return a;
return gcd(b, a % b);
Expand All @@ -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;
}
Expand Down

0 comments on commit 03b6906

Please sign in to comment.