문제 : https://www.acmicpc.net/problem/2478
2478번: 자물쇠
처음 k-왼쪽밀기의 k를 첫째 줄에, (p,q)-구간뒤집기의 p와 q를 빈칸을 사이에 두고 둘째 줄에, 그리고 마지막 k-왼쪽밀기의 k를 셋째 줄에 출력한다. 만일 답이 여럿일 경우에는 그 중 하나만 출력
www.acmicpc.net
문제 파악하기
주어진 수열을 만들기 위해 필요한 k-왼쪽밀기와 (p, q)-구간뒤집기 작업을 구하는 문제입니다. 모든 자물쇠는 [ k-왼쪽밀기 > (p, q)-구간뒤집기 > k-왼쪽밀기 ] 순서로 작업이 이루어지기 때문에, 우리는 반대로 [ k-오른쪽밀기 > (p, q)-구간뒤집기 > k-오른쪽밀기 ] 순서로 작업을 하여 정렬된 수열의 상태(1, 2, 3, ... , n)를 만들면 됩니다.
문제 해결하기
문제를 해결하기 위해서는 어느 구간에서 뒤집기가 일어났는지 파악하면 됩니다. 그럼 뒤집기가 일어난 구간은 어떻게 찾을 수 있을까요? 바로, 역순으로 정렬된 구간을 찾으면 됩니다. 정렬된 수열에서 뒤집기 작업을 한 경우에는 역순으로 정렬된 형태로 수열이 변하며, 우리는 역순으로 정렬된 구간을 찾아서 뒤집기 작업을 하면 됩니다. 다만, 역순으로 정렬된 구간을 찾을 때에 순진하게 첫 번째 숫자부터 차례대로 확인하면 안됩니다. 예를 들어, 다음과 같은 수열이 있다고 가정해봅시다.
6 5 4 3 9 10 1 2 8 7
위의 수열의 경우, 역순으로 정렬된 구간은 [ 8 7 6 5 4 3 ]까지 입니다. 하지만 왼쪽밀기 연산 때문에 인덱스 순서대로 역순으로 정렬되지 않습니다. 그렇기 때문에, 수열에서 모든 경우의 수를 확인해야 합니다.
이렇게 역순으로 정렬된 구간을 찾았다면 나머지 할 일은 적절하게 k-오른쪽밀기를 적용하면 됩니다. 여기서 말하는 적절하다는 의미는 뒤집을 구간이 항상 연속된 순서로 되어있으며, 뒤집기 연산이 일어난 후에 1의 위치가 첫 번째에 위치하지 않는 상황을 의미합니다. 수열을 하나씩 밀어보면서 적절한 k-오른쪽밀기 작업이 완료되면, 마지막으로 1의 위치를 찾아서 k-오른쪽밀기를 작업하면 됩니다.
다만, 모든 수열의 값이 역순으로 되어있다면 별도로 작업해야 합니다. 1의 위치가 n번째에 위치하지 않도록 k-오른쪽 밀기를 작업한 다음, 전체 수열을 뒤집고, 마지막으로 1의 위치를 찾으면 됩니다. 자세한 소스코드는 다음과 같습니다.
소스코드
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
102
103
104
105
|
#include <stdio.h>
#define NMAX 500*2+1
int origin[NMAX], convert[NMAX];
int n;
int l, r;
int t, tmp_cnt, cnt;
void k_push() {
for(int i=0;i<n;i++) origin[i] = convert[i];
for(int i=0;i<n;i++) convert[(i+1)%n] = origin[i];
}
void pq_flip(int p, int q) {
for(int i=0;i<n;i++) origin[i] = convert[i];
for(int i=0;i<p;i++) convert[i] = origin[i];
for(int i=p,j=q;i<=j;i++,j--) {
convert[i] = origin[j];
convert[j] = origin[i];
}
}
int main() {
scanf("%d", &n);
for(int i=0;i<n;i++) {
scanf("%d", &origin[i]);
convert[i] = origin[i] = origin[i]-1;
}
for(int i=0;i<n;i++) {
t = convert[i];
tmp_cnt = 0;
for(int j=(i+1)%n;j!=i;j=(j+1)%n) {
if((t-1 + n)%n != convert[j]) break;
tmp_cnt++;
t = (t-1 + n)%n;
}
if(cnt < tmp_cnt) {
cnt = tmp_cnt;
l = i; r = (i+cnt)%n;
}
}
// 해결방법
// 1. 뒤집힌 구간 찾기 (뒤집히면 정렬되어있지 않음)
// 2. 항상 맨 뒤쪽에서 뒤집기
// 3. 뒤집은 후 1의 위치 찾기
if(r-l+1 == n) {
int p1, p2;
// 1. 마지막 왼쪽밀기 (뒤집힌 구간 찾기)
p1 = 0;
do {
k_push();
p1++;
}while(convert[n-1]==0);
// 2. 구간 뒤집기 (항상 맨 끝에서 뒤집기)
pq_flip(0, n-1);
// 3. 첫 왼쪽밀기 (1의 위치 찾기)
for(p2=0;convert[p2]!=0;p2++);
printf("%d\n", n-p2);
printf("%d %d\n", 1, n);
printf("%d", p1);
}
else {
int p1, p2;
// 1. 마지막 왼쪽밀기 (뒤집힌 구간 찾기)
for(p1=1;p1<n;p1++) {
k_push();
l = (l+1)%n; r = (r+1)%n;
if(l>r) continue;
else if((l==0 and !convert[r]) or !convert[0]) continue;
else break;
}
// 2. 구간 뒤집기
pq_flip(l, r);
// 3. 첫 왼쪽밀기 (1의 위치 찾기)
for(p2=0;convert[p2]!=0;p2++);
printf("%d\n", n-p2);
printf("%d %d\n", l+1, r+1);
printf("%d", p1);
}
}
/*
10
4 3 2 1 10 9 8 7 6 5
12
12 11 10 9 8 7 6 5 4 3 2 1
*/
|
cs |
후기
역순으로 정렬된 부분이 수열의 앞부분(0번 인덱스)으로 넘어갈 수 있다는 걸 미처 생각하지 못해 답답했던 문제입니다. 하지만, 스페셜 저지 문제이기 때문에 내가 설계한 방법대로 문제를 해결할 수 있다는 점이 매력적인 문제입니다.
'문제 노트 > 정올' 카테고리의 다른 글
오목( BOJ 2615 ) (0) | 2022.06.26 |
---|---|
보석( BOJ 2492 ) (0) | 2022.06.19 |
전깃줄 - 2( BOJ 2568 ) (0) | 2022.04.14 |
공통 부분 수열 확장( BOJ 21762 ) (0) | 2022.04.12 |
그래프 균형 맞추기( BOJ 22344 ) (0) | 2022.03.30 |