USB 프로토콜의 전송 형태(Transfer Type)에 대해서 알아보자!
USB 전송 형태(Transfer Type)
USB 프로토콜을 제대로 이해하기 위해서 공부할 것들이 몇 가지 있다.
그 중에서 꼭 알아야 하는 것은 바로 USB의 전송 형태(Transfer Type)이다. 기본적으로 USB 프로토콜은 호스트(PC)와 디바이스(USB 장치)와의 통신에 관한 것이다.
구체적으로 그림과 함께 설명할 예정인데, 참고해야 할 점은 '회색(Gray)' 부분은 호스트(PC)가 보내는 패킷이고, '하얀색(White)' 부분은 디바이스(Device)가 보내는 패킷이다.

1. 등시성 전송(Isochronous Transfer)
첫 번째로 알아 볼 USB 전송 타입은 등시성 전송 타입이다. 이는 데이터의 전송 시간을 보증할 때 사용하는 타입이다. 데이터에 오류가 있어도 재전송을 하지 않으며, 호스트(PC)에서는 등시성 전송을 위해 시간을 스케줄링한다.
일반적으로 이어폰과 같이 실시간으로 데이터를 받아서 송출해야 하는 디바이스에서 사용된다. 생각해 보면, 이어폰처럼 실시간으로 데이터를 주고 받아야 하는 경우에서 데이터에 약간의 오류가 있다고 재전송을 시킬 수는 없을 것이다.
아무튼 등시성 전송은 다음과 같이 토큰 패킷과 데이터 패킷으로 처리된다. 흔히 이어폰의 경우에는 호스트가 OUT 패킷을 보냄으로써 "나 지금 음악 스트림 보낼거야."라고 말한 뒤에, 이어폰 디바이스한테 DATA 패킷을 연달아 보내는 것이다. 여기에서 토큰(Token) 패킷이란, 데이터의 방향을 알려주기 위해 호스트가 디바이스에게 보내는 패킷이다.
참고: OUT 패킷은 말 그대로 호스트 PC가 디바이스에게 보낼 데이터가 있다는 의미이다. 호스트 입장에서 받아야 할 데이터가 있다면 호스트가 먼저 IN 패킷을 보낸 뒤에, 디바이스로부터 데이터를 받게 된다.

2. 인터럽트 전송(Interrupt Transfer)
작은 크기의 데이터를 전달할 때 사용하는 타입이다. 일반적으로 마우스와 같이 클릭할 때만, 일시적으로 호스트(PC)가 데이터를 읽어들이는 경우에서 사용된다. 예시로는 키보드, 마우스 등이 있다.
인터럽트 전송은 말 그대로 비주기적인 형태의 전송이다. 인터럽트 요청은 디바이스에 의해서 차례대로 큐에 담기게 되고, 호스트가 USB 디바이스한테, "너 보내 줄 데이터 있니?"라고 물어보면, 그 때 큐에 담겨진 인터럽트 요청들이 디바이스에서 호스트로 보내진다.
예를 들어 우리가 키보드를 PC에 꽂은 뒤에, 타자를 친다고 가정해보자. 우리가 키보드에 입력한 데이터들은 일시적으로 키보드 디바이스의 내부 메모리 큐에 담기게 되고, 호스트가 폴링(Polling)을 하게 되면 그제서야 호스트에서 우리가 입력했던 문자들을 받아들이게 된다. (여기에서 알 수 있듯이, 호스트 입장에서는 주기적으로 폴링을 해야 한다.) 어느 정도의 주기로 폴링을 할 지는 디바이스의 엔드포인트 디스크립터(Endpoint Descriptor)에 명시되어 있다.
구체적인 과정은 다음과 같다.
▶ IN 토큰 패킷: 호스트가 데이터를 받는 경우이다. 호스트가 IN 패킷을 보내면, 디바이스의 큐에 데이터가 저장된 경우 데이터를 보내주게 된다. 데이터를 잘 받았으면 ACK를 보낸다. 디바이스의 큐가 비어있으면 (인터럽트 메시지가 없으면) 디바이스에서는 NAK를 보낸다. 그리고 오류가 발생한 상황에서는 디바이스가 STALL을 보내는데, 이러면 호스트가 IN 패킷을 다시 보낸다. 군더더기 없이 깔끔하다.
▶ OUT 토큰 패킷: 호스트가 데이터를 보내는 경우이다. 호스트가 디바이스로 데이터를 보내는 경우에는, OUT 패킷을 발행한 뒤에 데이터를 보낸다. 디바이스 입장에서는 데이터를 차례대로 받는데, 이는 엔드포인트 버퍼에 담기게 된다. 아직 이전 패킷을 다 처리하지 못해서 엔드포인트 버퍼가 비어 있지 않다면, 디바이스는 NAK를 보낸다. 마찬가지로 오류 상황일 때는 STALL을 보낸다.

3. 벌크 전송(Bulk Transfer)
대량의 데이터를 신뢰성 있게 전달할 때 사용하는 타입이다. 데이터 패킷 전송 과정에서 오류가 발생하면 재전송을 요구하며, 등시성 전송과는 다르게 전송 속도는 보증하지 않는다. 대표적인 예시로는 USB 저장 장치(Mass Storage)가 있다.
기본적으로 Bulk Transfer와 Interrupt Transfer는 사실상 같은 트랜잭션 형태를 보인다. 그림 자체도 비슷한 걸 확인할 수 있는데, 벌크와 인터럽트의 차이점은 인터럽트에서는 전송요구에 대한 시간의 주기를 설정할 수 있다는 것이다. 즉, 호스트가 주기적으로 폴링을 수행하도록 요구할 수 있다.
예를 들어 우리가 마우스를 클릭했는데 0.2초 뒤에 클릭이 된 것처럼 처리가 된다면 매우 불편할 것이다. 반면에 대용량 파일을 보내는 과정에서 0.2초 정도의 딜레이는 큰 문제가 없을 것이다. 더불어 Bulk Transfer에서는 1번의 트랜잭션으로 데이터가 전부 전송되지 않을 수 있기 때문에, 데이터를 분할해서 트랜잭션을 반복한다.

4. 제어 전송(Control Transfer)
USB 기기에게 명령을 내리거나 상태 관련 연산을 수행하기 위해 사용하는 타입이다. 모든 USB 기기는 제어 전송을 위해 엔드포인트(Endpoint) 0번을 사용하며, 어떤 데이터를 보낼지 또한 USB 규격으로 정해져 있다. 제어 전송의 경우 모든 USB 기기가 사용하는 전송 타입이다. 기본적으로 처음에 USB 디바이스를 PC에 꽂았을 때부터, 제어 전송을 통해 디스크립터(Descriptor) 정보를 받게 되기 때문이다. 이러한 작업들을 수행하는 것이 바로 제어 전송(Control Transfer)이다.
기본적으로 제어 전송은 3가지 단계로 구성된다. 차례대로 설정 단계(Setup Stage), 데이터 단계(Data Stage), 상태 단계(Status Stage)이다.
1) 설정 단계(Setup Stage): 제어 전송에서 가장 기본적인 단계이다. 이 단계는 초기 설정을 수행하며 세 개의 패킷(Token, Data, Handshake)으로 구성된다. 먼저 토큰 패킷으로는 설정을 진행할 Address 정보와 Endpoit 번호가 담기게 된다. 이후에 정확히 어떤 내용을 설정할 것인지 데이터 패킷에 담아서 보낸다. 예를 들어, 처음에 USB 디바이스에 연결된 상태라면 Descriptor 정보를 보내달라는 내용을 데이터 패킷에 담아서 보낼 것이다.

2) 데이터 단계(Data Stage): 데이터 단계는 필요하다면 존재할 수 있다. 만약 데이터 단계가 있다면, 설정 단계에서 '데이터 단계에서 얼마나 많은 패킷이 전달될 지' 미리 설정이 된다. 데이터 단계는 다수의 IN 토큰 (호스트가 데이터를 받는 경우)과 OUT 토큰 (호스트가 데이터를 보내는 경우)으로 구성되는 경우가 많다.
예를 들어 '설정 단계'에서 디바이스에게 Descriptor 정보를 달라는 패킷을 보냈다면, 이 데이터 단계에서는 IN 토큰을 발행해서 디바이스로부터 Descriptor 정보를 받게 된다.

3) 상태 단계(Status Stage): 제어 요청의 처리 상태를 보고하기 위해 사용되는 단계이다. 즉, 정상적으로 제어 전송이 완료되었는지를 확인하는 마지막 단계라고 할 수 있다.
데이터 단계에서 호스트가 IN 토큰을 보냈다면, 호스트 입장에서는 데이터를 모두 다 정상적으로 받았는지 궁금하다. 그래서 OUT 토큰과 길이가 0인 데이터 패킷을 하나 보내서, 디바이스한테 "너 데이터 다 보낸 거야?"라고 물어본다. 그래서 디바이스에서 데이터를 다 보냈다면 호스트에게 ACK를 보냄으로써 현재의 COMMAND가 종료되었음을 알린다. 혹은 NAK를 보내서 아직 더 처리할 게 남았다는 것을 알리게 된다.
반면에 데이터 단계에서 호스트가 OUT 토큰을 보냈다면, 정상적으로 데이터가 다 보내졌는지 확인하기 위해서 IN 토큰을 하나 보내서 디바이스에게 정상적으로 통신이 완료된 건지 물어보는 것이다.
* 상태 단계에서 OUT 토큰을 보내는 경우는 다음과 같다. (데이터 단계에서 호스트가 IN 토큰을 보낸 경우)

* 상태 단계에서 IN 토큰을 보내는 경우는 다음과 같다. (데이터 단계에서 호스트가 OUT 토큰을 보낸 경우)

USB 기기 클래스(Device Class)
USB 기기 클래스에 따라서 사용하는 전송 형태(Transfer Type)에도 차이점이 있다. 앞서 언급했듯이, 마우스나 키보드와 같은 디바이스들은 인터럽트 전송 타입을 많이 사용할 것이다. USB 기기 클래스를 간략히 소개하면 다음과 같은 것들이 있다.
* MSC (Mass Storage Class): USB 외부 저장장치에 대한 클래스로, Bulk Transfer를 사용한다.
* ADC (Audio Device Class): 오디오 장치에 대한 클래스로, Isochronous Transfer를 사용한다.
* HID (Human Interface Device): I/O 장치(키보드, 마우스) 같은 것에 대한 클래스로, Interrupt Transfer를 사용한다.
이외에도 MTP (Media Transfer Protocol)과 같은 클래스도 있다. 이 클래스는 MSC (Mass Storage Class)의 대용으로 사용될 수 있다. 기본적으로 USB 시스템에서는 Host가 주도권을 가지게 되는데, MSC의 경우 호스트가 마운트를 하고 나면, 해당 USB 저장 장치에 대해서 절대적인 제어권을 가진다. MSC는 블록 인터페이스 수준에서 동작하기 때문이다.
반면에 MTP는 논리적인 파일 수준의 송수신이 가능하다. 예를 들어 USB를 MTP로 제공할 때는, 특정 파일에 접근할 때는 암호를 입력하도록 하거나 할 수도 있다. 즉, MTP나 MSC나 모두 내부적으로 Bulk Transfer와 비슷한 기능을 이용하지만 목적 자체가 다르다는 점이 특징이다.
'기타' 카테고리의 다른 글
| MTP(Media Transfer Protocol) 라이브러리를 이용하여 Teensy를 USB Storage처럼 사용해보자! (0) | 2020.05.12 |
|---|---|
| USB(Universal Serial Bus) 프로토콜의 기초 지식 (0) | 2020.05.11 |
| SD 카드의 파일(이미지, 동영상)이 삭제되어 있고 정체 불명의 파일(깨진 파일)이 존재할 때, 복구 방법 (6) | 2020.05.06 |
| 가상 번호 만들어서 카카오톡 계정 2개 사용하기 (+ 카카오톡 계정 전화번호 교체 방법) (2) | 2020.05.05 |
| 크롬(Chrome) 브라우저가 느리게 동작하거나 인터넷 접속이 안 될 때 해결 방법 모음 (ERR_TIMED_OUT) (3) | 2020.05.05 |
[코드포스] Codeforces Round #639 (Div. 2)
대회 링크: https://codeforces.com/contest/1345
A번 문제: https://codeforces.com/contest/1345/problem/A
[ 문제 해설 ]
이 문제는 그림을 그려가면서 다양한 케이스들을 확인해 보면, 문제 풀이 아이디어를 빠르게 떠올릴 수 있다.
행이나 열의 크기가 1이라면, 항상 퍼즐을 풀 수 있다. 그리고 행이나 열의 크기가 둘 다 2, 2일 때에도 항상 퍼즐을 풀 수 있다. 하지만 그 이외의 모든 경우에 대해서는 퍼즐을 풀 수 없다.
[ 정답 코드 ]
for _ in range(int(input())):
n, m = map(int, input().split())
# 행이나 열의 크기가 1이라면 풀 수 있음
if n == 1 or m == 1:
print("YES")
# 행과 열의 크기가 모두 2일 때 풀 수 있음
elif n == 2 and m == 2:
print("YES")
# 이외의 경우에는 모두 풀 수 없음
else:
print("NO")
B번 문제: https://codeforces.com/contest/1345/problem/B
이 문제는 N장의 카드를 가지고 있을 때, 최대한 높은 층의 카드 쌓기를 반복적으로 수행하는 문제이다. 그래서 마지막에 몇 개의 카드 피라미드가 존재할 지 출력하면 된다.
아래의 그림에서 볼 수 있듯이,
1층짜리 피라미드: 2장 필요
2층짜리 피라미드: 7장 필요
3층짜리 피라미드: 15장 필요

그래서, 예를 들어 N = 24라고 해보자.
처음에 15장을 투자해 3층짜리 피라미드를 짓고, 9장이 남는다.
그리고 7장을 투자해 2층짜리 피라미드를 짓고, 2장이 남는다.
남은 2장을 투자해 1층짜리 피라미드를 짓고, 0장이 남는다.
그래서 결과적으로 피라미드를 3개 지을 수 있게 된다.
[ 문제 해설 ]
이 문제를 해결하기 위한 방법은 간단하다. 문제에서 요구하는 내용을 그대로 구현하면 된다. 이 때 피라미드는 층이 증가할수록 다음과 같이 카드가 필요하다는 특징이 있다.
- 1층 피라미드: 2장
- 2층 피라미드: 2장 + 5장
- 3층 피라미드: 2장 + 5장 + 8장
- 4층 피라미드: 2장 + 5장 + 8장 + 11장
- 5층 피라미드: 2장 + 5장 + 8장 + 11장 + 14장
즉, 층이 올라갈수록 '추가적으로 필요한 카드의 개수는 3장씩 증가'한다.
[ 정답 코드 ]
for _ in range(int(input())):
n = int(input())
result = 0
# 남아있는 카드가 2장 이상이라면, 피라미드 만들기 수행
while n >= 2:
# 초기 상태
summary = 0
count = 2
height = 0
# 더 높이 쌓을 수 없을 때까지 쌓기
while n >= summary + count:
summary += count
count += 3
height += 1
# 피라미드 하나를 완성했으므로, n에서 필요한 카드의 개수만큼 빼주기
result += 1
n -= summary
print(result)
C번 문제: https://codeforces.com/contest/1345/problem/C
이 문제는 비둘지깁의 원리같은 느낌의 문제다.
'알고리즘 대회' 카테고리의 다른 글
| [앳코더] AtCoder Beginner Contest #178 (0) | 2020.09.14 |
|---|---|
| [코드포스] Codeforces Round #669 (Div. 2) (0) | 2020.09.10 |
| [코드포스] Codeforces Round #640 (Div. 4) (0) | 2020.05.10 |
| 코드포스(Codeforces) 대회에서 소스코드 해킹(Hack) 방법 (0) | 2020.05.10 |
| [코드포스] Codeforces Round #637 (Div. 2) (0) | 2020.04.25 |
[코드포스] Codeforces Round #640 (Div. 4)
대회 링크: https://codeforces.com/contest/1352
A번 문제: https://codeforces.com/contest/1352/problem/A
[ 문제 해설 ]
이 문제는 먼저 각 자릿수를 확인하면 된다. 이 때, 0이 아닌 자릿수에 대하여 그 값들을 출력해주면 된다. 예를 들어 입력이 6009이라면, 6000과 9를 출력하면 된다. 소스코드는 다음과 같다.
[ 정답 코드 ]
for _ in range(int(input())):
n = int(input())
result = []
power = 1
# 각 자릿수를 확인하며
while n > 0:
# 0이 아닌 자릿수에 대하여
if n % 10 != 0:
# 그 값들을 각각 출력
result.append((n % 10) * power)
n //= 10
power *= 10
print(len(result))
for i in result:
print(i, end=' ')
print()
B번 문제: https://codeforces.com/contest/1352/problem/B
이 문제는 k개의 수를 더해서 n을 만들 수 있는지 물어보는 문제이다. 단, k개의 수는 모두 홀수이거나 모두 짝수여야 한다.
[ 문제 해설 ]
문제에서 요구하는 대로 모든 수가 홀수이거나, 모든 수가 짝수인 경우 두 가지만 고려하면 된다.
먼저 모든 수가 홀수인 경우, (k - 1)개의 수를 모두 1로 설정(가능한 작은 수)한 뒤에 '나머지 1개의 수'가 홀수이면서 조건을 만족시킬 수 시 있는지 확인하면 된다. 예를 들어 n = 7, k = 5인 경우에는 (k - 1) + 3 = 7로 n을 만족시킬 수 있다. 즉, 나머지 1개의 수를 3으로 설정하면 된다.
그리고 모든 수가 짝수인 경우, (k - 1)개의 수를 모두 2로 설정(가능한 작은 수)한 뒤에 '나머지 1개의 수'가 짝수이면서 조건을 만족시킬 수 있는지 확인하면 된다. 예를 들어 n = 8, k = 3인 경우에는 (k - 1) * 2 + 4 = 8로 n을 만족시킬 수 있다. 즉, 나머지 1개의 수를 4로 설정하면 된다.
따라서 두 가지 경우만 잘 고려해주는 코드를 작성하면 된다.
[ 정답 코드 ]
for _ in range(int(input())):
n, k = map(int, input().split())
# 모든 수가 홀수인 경우
odd = n - (k - 1)
if odd > 0 and odd % 2 == 1:
print("YES")
for i in range(k - 1):
print(1, end=' ')
print(odd)
continue
# 모든 수가 짝수인 경우
even = n - (k - 1) * 2
if even > 0 and even % 2 == 0:
print("YES")
for i in range(k - 1):
print(2, end=' ')
print(even)
continue
# 두 가지 케이스 모두 만족시킬 수 없을 때
print("NO")
C번 문제: https://codeforces.com/contest/1352/problem/C
n과 k가 주어졌을 때, n으로 나누어 떨어지지 않는 k번째 자연수를 찾으면 되는 문제다.
예를 들어 n = 3, k = 7이라고 하면
1, 2, 4, 5, 7, 8, 10, 11, 13, ...
위와 같이 이어지므로, 7번째 수는 바로 10이 된다.
[ 문제 해설 ]
답을 구하는 방법은 생각보다 쉽다. 구하고자 하는 k번째 자연수는 항상 k 이상이고, 그리고 2k 미만이 된다.
생각해 보면, n = 2일 때는 1, 3, 5, 7, ... 이렇게 갈 텐데, 이 때에도 k번째 수는 2k보다 작은 2k - 1이 된다. 다시 말해 출력해야 되는 정답은 항상 k 이상 2k 미만의 수가 될 것이다. 그렇다면 k보다 얼마나 큰 수가 출력되어야 하는가?
그것은 바로 k보다 (k - 1) // (n - 1)만큼만 커지면 된다. 즉, k + (k - 1) // (n - 1)이 정답이다. 답을 계산하는 방법은 굉장히 다양하지만, 이것보다 간단히 수식으로 표현할 수 있는 답은 없다.
[ 정답 코드 ]
for _ in range(int(input())):
n, k = map(int, input().split())
print(k + (k - 1) // (n - 1))
D번 문제: https://codeforces.com/contest/1352/problem/D
왼쪽에서부터 오른쪽으로 과자를 먹는 사람과 오른쪽에서부터 왼쪽으로 과자를 먹는 사람이 있다.
이 때 한 차례씩 반복하면서 먹어나가는데, 이전 차례에서 상대방이 먹었던 양보다 더 많이 먹어야 하는 방식이다. 그래서 전체 몇 차례나 반복되었는지, 두 사람이 각각 얼마나 먹었는지를 출력하면 된다.
[ 문제 해설 ]
이 문제는 문제에서 요구하는 대로 구현만 잘 해주면 된다. 최대한 깔끔하게 실수 없이 구현해서 정답 처리를 받으면 된다.
[ 정답 코드 ]
for _ in range(int(input())):
n = int(input())
data = list(map(int, input().split()))
l = 0
r = n - 1
left_sum, right_sum = 0, 0
left_res, right_res = 0, 0
count = 0
# 일종의 투 포인터(Two Pointers) 기법
while l <= r:
# 왼쪽에서 먹을 차례
if count % 2 == 0:
now_sum = 0
while l <= r and now_sum <= right_sum:
now_sum += data[l]
l += 1
left_res += now_sum
left_sum = now_sum
# 오른쪽에서 먹을 차례
elif count % 2 == 1:
now_sum = 0
while l <= r and now_sum <= left_sum:
now_sum += data[r]
r -= 1
right_res += now_sum
right_sum = now_sum
count += 1
print(count, left_res, right_res)
E번 문제: https://codeforces.com/contest/1352/problem/E
n개의 정수로 구성된 수열이 있을 때, 각 n개의 수에 대하여 그 수와 동일한 합을 가지는 '부분 연속 수열'이 존재하는지 찾으면 되는 문제이다.
예를 들어 [3, 1, 4, 1, 5, 9, 2, 6, 5]가 있을 때 6번째 수인 9는 [3, 1, 4, 1]의 합과 같다.
[ 접근 방법 1 ]
일단 이 문제는 투 포인터(Two Pointer)를 이용하여 해결할 수 있다. n개의 수 각각에 대하여 투 포인터 알고리즘을 돌리면 된다. 투 포인터 문제로 유명한 '합이 M인 부분 연속 수열 찾기' 코드를 그대로 쓰면 된다.
단, 이 문제에서는 부분 연속 수열의 길이가 2 이상이 되라고 하였으므로 투 포인터 알고리즘을 길이가 2 이상인 경우만 찾도록 수정해서 이용하면 된다.
코드포스 문제를 풀다 보면, 투 포인터 문제가 굉장히 자주 나오는 것 같다. 혹은... 내가 그냥 해법으로 투 포인터를 먼저 생각해서 그러는 거일 수도 있겠다. 근데 이게 가끔은 시간 초과를 받는 경우도 있으니 조심해야 한다.
for _ in range(int(input())):
n = int(input())
data = list(map(int, input().split()))
result = 0
for x in data:
summary = 0
end = 0
for start in range(n):
while summary < x and end < n:
summary += data[end]
end += 1
if summary == x and end - start >= 2:
result += 1
break
summary -= data[start]
print(result)
근데... 걱정했던 대로 위 코드는 시간초과를 받았다. 투 포인터를 이용할 때에도 사실 O(N^2)의 해법으로 시간 복잡도가 똑같기 때문에 괜찮을 줄 알았는데, 파이썬을 이용하고 있기 때문인지... 시간 초과를 받을 줄은 몰랐다. ㅠㅠ 시간 초과를 받은 케이스를 보니까 정말 간발의 차이로 시간 초과를 받았다. 0.5초만 더 널널했어도, 시간 초과를 받지 않았을 것 같다.
[ 접근 방법 2 ]
아무튼 같은 O(N^2)이지만, 조금 더 빠른 해법은 모든 구간의 합을 하나의 배열에 기록해 놓는 방법이다. (계수 정렬 할 때 처럼) 모든 값들이 n (1 <= n <= 8000) 보다 작기 때문에 이 또한 가능하다. 시간 초과에 걸리지 않는, 정답 코드는 다음과 같다.
[ 정답 코드 ]
for testcase in range(int(input())):
n = int(input())
data = list(map(int, input().split()))
# 모든 구간 합을 저장하기 위한 집합 자료형
visited = set()
# 길이가 2 이상인 모든 구간 합을 집합에 저장
for i in range(n):
summary = data[i]
for j in range(i + 1, n):
summary += data[j]
if summary > n:
break
visited.add(summary)
# 각 값을 확인하며, 계산할 수 있는 구간 합인 경우 카운트
result = 0
for x in data:
if x in visited:
result += 1
print(result)
F번 문제: https://codeforces.com/contest/1352/problem/F
[ 문제 해설 ]
이 문제도 구현 문제인데, 최대한 구현 실수가 발생하지 않게 깔끔한 아이디어로 푸는 것이 중요하다. 일단 가장 중요한 게 n1 = 0인 케이스이다. 이 때는 n0과 n2 둘 중에 하나는 0이 되어야 한다. 안 그러면 조건이 만족되지 않는다. 따라서 n1 = 0인 경우에는, 단순히 n0과 n2 중에 하나만 0이 아닌 값을 가진다는 것을 고려해서 풀면 된다.
그리고 n1 = 0인 케이스가 아니라면, 다음과 같이 먼저 처리해서 문제를 해결할 수 있다.
해결법은 바로 다음과 같다. 먼저 (n2 + 1)의 크기만큼 1을 출력한다. 그리고 n0의 크기만큼 0을 출력한다. 이후에 n1의 크기만큼 010101... 을 반복해서 출력하면 된다. 그러면 정확히 조건을 만족할 수 있다.
[ 정답 코드 ]
for testcase in range(int(input())):
n0, n1, n2 = map(int, input().split())
# n1가 0인 특이 케이스 처리
if n1 == 0:
if n0 > 0:
print("0" * (n0 + 1))
else:
print("1" * (n2 + 1))
continue
# 일반적인 케이스 처리
print("1" * (n2 + 1), end="")
print("0" * n0, end="")
for i in range(n1):
if i % 2 == 0:
print("0", end="")
else:
print("1", end="")
print()
G번 문제: https://codeforces.com/contest/1352/problem/G
[ 문제 해설 ]
이 문제도 간단한 아이디어를 떠올리면 간단히 풀 수 있다. 필자는 대회 당시에는 매우 복잡하게 푼 것 같은데, 실제로는 매우 간단하게도 문제를 해결할 수 있었다.
홀수를 먼저 내림차순으로 출력한 뒤에 4와 2를 출력하고, 다시 모든 짝수를 오름차순으로 차례대로 출력하면 된다. 이렇게 대칭을 이루도록 출력하면 간단하게 해결된다.
예를 들어 n = 11이라고 하면,
[ 11 9 7 5 3 1 ] [ 4 2] [6 8 10]로 출력하면 된다.
위 출력 결과를 보면 인접한 모든 수들은 2 이상, 4 이하로 차이가 난다.
[ 정답 코드 ]
for _ in range(int(input())):
n = int(input())
# 4보다 작은 경우, 만들 수 없음
if n < 4:
print(-1)
continue
# 모든 홀수를 먼저 내림차순으로 출력
for i in range(n, 0, -1):
if i % 2 == 1:
print(i, end=' ')
# 4와 2를 출력
print(4, 2, end=' ')
# 모든 짝수를 오름차순으로 출력
for i in range(6, n + 1, 2):
print(i, end=' ')
print()
'알고리즘 대회' 카테고리의 다른 글
| [코드포스] Codeforces Round #669 (Div. 2) (0) | 2020.09.10 |
|---|---|
| [코드포스] Codeforces Round #639 (Div. 2) (0) | 2020.05.11 |
| 코드포스(Codeforces) 대회에서 소스코드 해킹(Hack) 방법 (0) | 2020.05.10 |
| [코드포스] Codeforces Round #637 (Div. 2) (0) | 2020.04.25 |
| [코드포스] Codeforces Round #634 (Div. 3) (0) | 2020.04.23 |
코드포스(Codeforces) 대회에서 소스코드 해킹(Hack) 방법
코드포스의 경우 다른 사람의 소스코드를 해킹(Hack)할 수 있는 기능이 제공된다. 사실 처음에는 알고리즘 대회인데 해킹 시스템이 왜 있는지 궁금했다. 해킹은 다른 사람의 소스코드를 지적하는 의미가 되기도 하고, 알고리즘 대회 자체가 가지고 있는 채점 데이터 셋의 오류를 지적하는 의미가 되기도 한다.
일단 코드포스에서는 대회 도중에 해킹(Hacking)하는 경우와, 대회가 끝난 뒤에 12시간 동안 오픈 해킹(Open Hacking)을 하는 경우가 있다. 방법은 기본적으로 유사하다. 일단 상대방의 소스코드를 확인한 뒤에 문제가 있다고 판단이 되면, 소스코드에서 제대로 처리하지 못할 입력(Input)을 만들어서 입력하면 된다.
내 코드를 잠금(Lock) 했거나, 오픈 해킹 기간일 때는 다음과 같이 다른 사람들의 소스코드를 확인할 수 있다. 기간 안에는 이러한 소스코드에 해킹을 시도할 수 있다.

다른 사람의 소스코드를 확인한 뒤에 [hack it!] 버튼을 눌러서 해킹을 시도하면 된다.

해킹을 시도할 때는 입력 문자열을 직접 넣거나, 입력 문자열이 담긴 파일을 업로드해서 제출하면 된다.

나는 한 번 자핵을 해보았다. 내가 낸 소스코드에 문제가 있는 것 같아서, 오픈 해킹 기간 때 내 소스코드를 직접 공격해보았다. 아무튼 해킹에는 성공했다.
해킹에 성공한 경우 다음과 같이 "Successful hacking attempt"라는 메시지가 나온다. 해킹에 실패한 경우 "Unsuccessful hacking attempt"라고 나오며, 입력 자체가 잘못된 경우에는 "Invalid input"이라는 메시지가 나온다.

해킹에 성공한 경우, 해킹을 당한 쪽은 다음과 같은 메시지가 출력된다.

다음과 같이 스탠딩(Standing)에서는 "해킹 성공 횟수 : 해킹 실패 횟수" 정보가 출력된다.

해킹 시스템은 상당히 재미있는 것 같다. 해킹 시스템이 있기 때문에, 채점 데이터셋은 더욱 견고해진다. 실제로 간당간당하게 시간 초과나 메모리 초과를 벗어난 코드들은 채점 데이터가 조금 더 추가되면, 운에 따라서 오답 코드로 처리되기도 한다. 나는 최근에 Pypy로 문제를 풀고 있는데, C++에서는 정답 처리가 될 법한 코드가 가끔씩 TLE를 받기도 한다.
[ 해킹용 입력 데이터 만드는 예제 코드 ]
import random
f = open("test.txt", 'w')
# The number of test cases
f.write('1\n')
# Input: N
f.write('10000\n')
for i in range(10000):
# Make random data.
f.write(str(random.randrange(1, 1001)) + ' ')
f.close()'알고리즘 대회' 카테고리의 다른 글
| [코드포스] Codeforces Round #639 (Div. 2) (0) | 2020.05.11 |
|---|---|
| [코드포스] Codeforces Round #640 (Div. 4) (0) | 2020.05.10 |
| [코드포스] Codeforces Round #637 (Div. 2) (0) | 2020.04.25 |
| [코드포스] Codeforces Round #634 (Div. 3) (0) | 2020.04.23 |
| [코드포스] Codeforces Round #636 (Div. 3) (0) | 2020.04.23 |
SD 카드의 파일(이미지, 동영상)이 삭제되어 있고 정체 불명의 파일(깨진 파일)이 존재할 때, 복구 방법
나는 최근에 디지털 카메라에 있는 동영상을 옮기기 위해서 SD 카드를 컴퓨터에 꽂았다. 그러다가 어느 순간에 보니까, 동영상 파일 몇 개가 사라져 있었다. 그리고 당황스러운 점은, 아래와 같이 "USBC族Q"라는 이름을 가지는 파일이 생성되어 있었다. 그냥 깨진 이름일 뿐 그다지 유의미한 이름은 아닌 것 같다.
내 생각에는 디지털 카메라의 전원이 없어서 갑자기 종료가 되었거나, 아니면 SD 카드를 갑자기 컴퓨터에서 뽑거나, 혹은 SD카드 USB 리더기의 문제일 수도 있을 것 같다. 아무튼 이유가 무엇이든 데이터가 유실된 것으로 보인다. 데이터가 유실되는 과정에서 동영상 파일들이 하나의 파일로 합쳐진 게 아닐까 생각하고 있다. "USBC族Q"라는 이름을 가진 파일의 크기가 3GB인 것을 보면, 분명 이 파일이 기존의 동영상 파일들을 가지고 있을 것 같다.
아무튼 확장자도 없고, 이 파일을 실행하는 것은 불가능해 보인다.

그래서 데이터 복구 프로그램을 이용하기로 했다. 미디어 파일 (MP4, JPG 등)을 전문으로 복구해주는 프로그램이 필요한 상황인데, 어떤 것을 이용할 지 고민을 해보았다. 일단 혹시 모르니까, 현재 눈에 보이는 SD 카드 내부의 모든 파일들을 PC 내 별도의 폴더로 옮겨주었다. (일단 남아 있는 파일들은 최소한 안전하게 가지고 있어야 하기 때문이다.)
1. 레쿠바(Recuva)
삭제된 파일을 복구하기 위한 유명한 복구 프로그램이다. 물론 나의 경우에는 파일을 삭제한 것이 아니라 파일의 메타 데이터에 문제가 생긴 것으로 판단이 들지만, 일단 가장 유명한 게 이것이기 때문에, 이를 사용해보기로 했다.
▶ 레쿠바 다운로드: https://software.naver.com/software/summary.nhn?softwareId=MFS_116447

설치 프로그램을 다운로드 받아서 [Install] 버튼을 클릭하여 설치를 진행할 수 있다.

설치가 완료되면 [Run Recuva] 버튼을 눌러서 레쿠바를 실행할 수 있다.

레쿠바를 실행하면 다음과 같이 설정 진행 창이 나온다.

복구하고자 하는 파일의 종류를 기록해준다.

이후에 어떤 저장장치에서 파일을 찾을 건지 설정할 수 있다. 여기에서 나의 SD 카드 드라이브의 경로를 넣어 주었다.

이제 복구를 해보자. [Enable Deep Scan]에 체크하여, 가능한 많은 파일들을 찾아낼 수 있도록 하자.

스캔이 끝나고 나면, 다음과 같이 복구할 수 있는 파일들의 리스트가 등장한다. 다만, 필자의 경우에는 삭제했던 파일들은 복구가 가능한 경우가 많았지만, 갑자기 사라져 버린 파일들에 대해서는 레쿠바(Recuva)가 찾지 못했다. 아마 삭제 자체를 하지는 않았기에, SD 카드에 그대로 남아 있는 영역이라서 레쿠바가 찾지 못하는 것 같다.

2. 이지어스 데이터 복구 (EaseUS Data Recovery)
그래서 다른 프로그램을 찾아보기로 했다. 그냥 인터넷에서 검색하다가 알게 된 소프트웨어인데, 일단 이것도 한 번 받아서 사용해보기로 했다.
▶ 설치 경로: https://www.easeus.co.kr/data-recovery-solution/recover-video-files-from-dashcam-memory-card.html
다음과 같이 [다운로드] 버튼을 눌러서 설치 프로그램을 다운로드 할 수 있다.

설치를 진행해보자. 설치를 진행한 뒤에 보니까, 특정 용량 이상으로 복구하기 위해서는 유료 결제를 해야한다고 한다.

아무튼 설치 이후에 디지털 카메라용 SD 카드를 선택해서 스캔을 해보았다.

다만 엄청나게도 [손실된 파일] 영역으로, 내가 잃어버린 파일들이 모두 찾아지는 것을 확인할 수 있었다. 심지어 복구를 해보았더니, 영상이 잘 살아나는 것을 확인하였다. 역시 유료 프로그램이라서 더 성능이 좋은 것일까?
그런데, 500MB 이상으로 복구하기 위해서는 유료 결제를 하라고 한다. 일단 시험 공부를 하러 가야 해서, 여기에서 잠깐 접고 나중에 무료 복구 프로그램이 있는지 더 찾아보려고 한다.

3. 체크디스크(CHKDSK) 명령 혹은 드라이브 검사 기능
일단 위에서 소개한 이지어스 소프트웨어는 상당히 좋은 솔루션이지만, 유료이기 때문에 다른 방법들을 찾아보았다.
윈도우(Windows) 운영체제는 기본적으로 체크디스크 명령을 지원한다. 이걸 사용하면 자동으로 손실된 파일들이 .CHK 확장자를 가지는 파일로 복구된다는 이야기를 들어서, 한 번 시도해보았다. 다음과 같이 [내 PC]로 이동해서 저장장치를 우클릭하여 [속성(R)]에 들어간다.

다음과 같이 [도구] - [검사(C)] 버튼을 눌러서 검사를 진행한다.

나는 드라이브에 오류가 없다고 나왔지만, 그래도 [드라이브 검사 및 복구] 버튼을 눌렀다.

그랬더니 성공적으로 복구가 되었다는 메시지가 나왔다.

근데 정작 DCIM 폴더로 들어가 보니까, 기존에 존재하던 105__04 폴더 자체가 사라졌다. 무언가 잘못된 것 같다는 직감이 들었다. 하지만 침착하게 해결 방법을 찾아 보았다.

검색을 해보니까 체크디스크(CHKDSK) 명령을 한 이후에는 일부 파일들이 자동으로 복구가 되기도 하는데, 그 복구가 된 파일은 FOUND.000 폴더에 들어가게 된다는 것이다. 또한 해당 폴더는 '숨김 폴더'라서 숨김 항목을 볼 수 있도록 체크해야 한다. 하지만, 필자의 경우에는 어이없게도 숨김 항목들을 확인해도 FOUND.000이 보이지 않았다.

속은셈 치고, USB 드라이브의 루트 폴더에서 'FILE'이라고 검색해보았다. 그랬더니 FOUND.000 폴더는 안 보이지만, 그 안에 있는 파일들은 보이게 되었다!

이제 파일을 우클릭하여 [파일 위치 열기(I)]를 해서 폴더를 열어보았다.

이제 FOUND.000 폴더에 있는 모든 복구된 파일들이 보이게 되었다. 이 파일들은 기본적으로 확장자 정보를 가지고 있지 않다. 그래서 추가적인 작업이 필요한데 나의 경우에는 이 파일들이 모두 동영상 파일(.mp4)이다. 그래서 그냥 확장자만 .mp4로 바꾸어주었더니 정상적으로 동작하게 되었다.

그래서 다음과 같이 동영상 파일(.mp4)로 변경하여 실행했더니 정상적으로 동작하게 되었다.

또한 동영상 파일의 메타 정보도 남아 있어서, '미디어 작성 날짜'도 확인할 수 있다.

4. 테스트디스크 (TestDisk)
앞서 설명한 이지어스의 경우 유료 프로그램이기 때문에, 무료 프로그램을 검색하다가 TestDisk를 찾았었다. 결국 이걸 사용하지 않고도 해결하긴 했지만, 이런 무료 프로그램도 있다는 것을 기록해 놓고자 한다. 아래 링크에서 다운로드가 가능하다.
▶ 설치 경로: https://www.cgsecurity.org/wiki/TestDisk_Download

'기타' 카테고리의 다른 글
| USB(Universal Serial Bus) 프로토콜의 기초 지식 (0) | 2020.05.11 |
|---|---|
| USB 프로토콜의 전송 형태(Transfer Type)에 대해서 알아보자! (0) | 2020.05.11 |
| 가상 번호 만들어서 카카오톡 계정 2개 사용하기 (+ 카카오톡 계정 전화번호 교체 방법) (2) | 2020.05.05 |
| 크롬(Chrome) 브라우저가 느리게 동작하거나 인터넷 접속이 안 될 때 해결 방법 모음 (ERR_TIMED_OUT) (3) | 2020.05.05 |
| 경기도 재난 지원금 (재난기본소득) 온라인 신청 방법 (0) | 2020.04.24 |
가상 번호 만들어서 카카오톡 계정 2개 사용하기 (+ 카카오톡 계정 전화번호 교체 방법)
최근에 방송을 할 때, 전화 상담 콘텐츠를 진행하고 있다. 다만 핸드폰 번호를 공개할 수는 없다 보니까, 카카오톡 계정을 하나 더 만들어서 보이스톡을 통해서 전화 상담 콘텐츠를 진행하는 것이 편한 것 같다.
※ 가상 번호 생성하기 ※
카카오톡과 같은 메신저 애플리케이션을 여러 계정으로 사용하기 위해서는, 여러 개의 전화번호가 필요한 경우가 많다. 그래서 통신사에서 2개의 번호를 이용하도록 해주는 서비스를 이용할 수도 있다. 하지만 그것보다는 가상 번호를 이용하는 게 더 편하기 때문에, 나는 가상 번호를 이용해보았다.
가상 번호를 생성해주는 서비스는 다양하다. 대표적으로 텍스트나우(Textnow)와 같은 서비스가 있다. 텍스트나우를 설치한 뒤에 회원가입 후에 로그인을 해보았다. 화면은 다음과 같은데, 로그인 이후에 가상 핸드폰 번호를 받을 수 있다.

가상 번호를 만들 때는, 미국 뉴욕(New York) 주에 속한 585를 지역 코드로 사용하는 것이 좋은 것 같다.

위와 같이 [Continue] 버튼을 누르면 가상 번호를 발급받을 수 있다.
※ 핸드폰으로 카카오톡 앱 2개 사용하기 ※
이제 발급 받은 번호를 이용해서, 카카오톡 계정을 생성할 수 있다. 추가적으로 알면 좋은 점은, 안드로이드 스마트폰에서는 동일한 메신저 앱을 2개로 늘려서 동시에 사용할 수 있도록 기능을 지원해주고 있다는 점이다. 안드로이드 스마트폰의 [설정] 창에서 [유용한 기능] 탭으로 이동해보자.

그러면 다음과 같이 [듀얼 메신저] 항목을 클릭하여 메신저 애플리케이션 중에서 듀얼 메신저를 지원하는 애플리케이션들을 확인할 수 있다.

결과적으로 카카오톡 애플리케이션을 두 번째 계정으로 이용할 수 있다. 이제 이 듀얼 메신저 카카오톡을 실행하여 가상 번호를 이용해 회원가입을 진행하면 된다.

※ 카카오톡 계정의 전화번호 변경하기 ※
또한 기본적으로 가상 전화번호는 시간이 지나면, 사용이 불가능해지는 경우가 많다. 따라서 카카오톡 계정의 [프로필 관리] 창에서 전화번호를 변경할 수 있다. 프로필 관리 창에서 [전화번호] 탭을 클릭해보자.

[전화번호 변경] 버튼을 눌러서 전화번호를 변경할 수 있다.

텍스트나우(Textnow)를 통해 새롭게 발급 받은 전화번호를 입력하여 인증하면, 성공적으로 전화번호가 변경된다.

이 방법을 이용하면 가상 번호로 만든 2번째 카카오톡 계정의 번호를 또 다른 가상 번호로 변경할 수 있기 때문에, 가상 번호 기간이 만료되었을 때 번호를 교체할 수 있다.
'기타' 카테고리의 다른 글
| USB 프로토콜의 전송 형태(Transfer Type)에 대해서 알아보자! (0) | 2020.05.11 |
|---|---|
| SD 카드의 파일(이미지, 동영상)이 삭제되어 있고 정체 불명의 파일(깨진 파일)이 존재할 때, 복구 방법 (6) | 2020.05.06 |
| 크롬(Chrome) 브라우저가 느리게 동작하거나 인터넷 접속이 안 될 때 해결 방법 모음 (ERR_TIMED_OUT) (3) | 2020.05.05 |
| 경기도 재난 지원금 (재난기본소득) 온라인 신청 방법 (0) | 2020.04.24 |
| 윈도우(Windows)에서 make 명령 사용하기 (0) | 2020.04.20 |
크롬(Chrome) 브라우저가 느리게 동작하거나 인터넷 접속이 안 될 때 해결 방법 모음 (ERR_TIMED_OUT)
윈도우 10(Windows 10)에서 크롬(Chrome) 브라우저를 이용하면 종종 느리게 동작하는 경우가 있다. 이럴 때는 공통적으로 해보면 좋은 해결 방법들이 있다. 물론 느리게 동작하는 데에는 다양한 이유가 있을 수 있는데, 일반적으로 다음의 방법들을 이용해 보면 거의 해결된다.
※ 공통적인 해결 방법 ※
크롬(Chrome)은 chrome://flags 경로로 들어가면, 실험적인 기능에 대한 사용 여부를 설정할 수 있다.

여기에서 기본적으로 설정을 기본 설정(Default)으로 변경할 수 있다. [Reset all to default]를 눌러서 적용할 수 있다.

이후에 Experimental QUIC protocol 기능을 [Disabled]로 설정하자.

또한 크롬(Chrome) 브라우저에서는 chrome://settings 경로로 이동하여 브라우저 [설정] 창으로 이동할 수 있다.

이제 [설정을 기본값으로 복원]을 눌러서 모든 설정 값을 기본 설정으로 변경하자.

다음과 같이 [설정 초기화]를 눌러서 초기화를 진행하면 된다.

또한 [가능한 경우 하드웨어 가속 사용] 기능을 꺼준다.

※ 윈도우 업데이트 이후에 ERR_TIMED_OUT 오류 ※
윈도우 10 (Windows 10) 이용자의 경우, 윈도우 업데이트 이후에 크롬을 실행하면 ERR_TIMED_OUT 오류가 나오며 접속이 안 되는 경우가 있다. 이는 윈도우 시스템 서비스 관련 이슈로 알려져 있다.

다음과 같이 작업 관리자를 실행한 뒤에, [서비스] 탭으로 이동하여 [Cryptographic Services]가 존재하는지 확인한다. 존재한다면, 우클릭 이후에 [서비스 열기]를 눌러서 서비스 창을 열 수 있다.

서비스(Services) 창이 켜지면 [Cryptographic Services]를 찾아서 우클릭하여 [속성(R)] 창을 열어준다.

이 때 [로그온] 탭으로 이동하여 [로컬 시스템 계정]란에서 [서비스와 데스크톱 상호 작용 허용]을 눌러주면 된다.

그리고 해당 서비스를 [다시 시작]해주면 된다.

이제 크롬이 다시 예전처럼 빠르게 동작하는 것을 확인할 수 있다.
'기타' 카테고리의 다른 글
| SD 카드의 파일(이미지, 동영상)이 삭제되어 있고 정체 불명의 파일(깨진 파일)이 존재할 때, 복구 방법 (6) | 2020.05.06 |
|---|---|
| 가상 번호 만들어서 카카오톡 계정 2개 사용하기 (+ 카카오톡 계정 전화번호 교체 방법) (2) | 2020.05.05 |
| 경기도 재난 지원금 (재난기본소득) 온라인 신청 방법 (0) | 2020.04.24 |
| 윈도우(Windows)에서 make 명령 사용하기 (0) | 2020.04.20 |
| 리눅스(Linux)에서 윈도우(Windows)용 실행 파일을 컴파일하는 방법 (크로스 컴파일) (0) | 2020.04.20 |
[코드포스] Codeforces Round #637 (Div. 2)
대회 링크: http://codeforces.com/contest/1341
A번 문제: http://codeforces.com/contest/1341/problem/A
이 문제는 n개의 쌀알의 무게의 범위가 각각 [a - b, a + b]일 때, 이 쌀알들의 무게를 모두 더한 것이 [c - d, c + d]에 들어갈 수 있는지 물어보고 있다.
입력으로는 매 테스트 케이스마다 n, a, b, c, d가 입력된다.
예를 들어 7 20 3 101 18이 들어오게 되면, 각 쌀알의 무게는 [17, 23] 범위 중 하나가 된다. 이 때, 모든 쌀알의 무게를 더한 것이 [83, 119] 범위 중 하나가 될 수 있어야 한다는 것이다. 이 경우, 각 쌀알의 무게가 17이라고 하면, 모든 쌀알의 무게를 더한 것은 119가 되므로 조건을 만족한다. 따라서 "Yes"를 출력하게 된다.
[ 문제 해설 ]
이 문제는 단순히 [(a - b) * n, (a + b) * n]이 [c - d, c + d]과 교차하는지(Intersecting) 확인하면 된다.
따라서, 일직선 상의 두 직선이 교차하는 지 확인하는 알고리즘을 사용하면 된다.
만약에 (line_1.left <= left_2.right and line_1.right >= line_2.left)이 성립한다면, 교차한다고 볼 수 있다.
[ 정답 코드 ]
for _ in range(int(input())):
n, a, b, c, d = map(int, input().split())
# 일직선상의 두 직선이 교차하는지 검사
if (a - b) * n <= c + d and (a + b) * n >= c - d:
print("Yes")
else:
print("No")
B번 문제: http://codeforces.com/contest/1341/problem/B
산에는 고점(Peek)들이 존재하는데, 뾰족하게 올라온 부분이라고 보면 된다. 이 때, 제일 왼쪽과 제일 오른쪽은 제외한다.
예를 들어 산 데이터가 [3, 1, 4, 1, 5, 9, 2, 6]이라고 하면, 3의 위치와 6의 위치에 고점(Peeks)들이 있는 것이다.
문제에서는 K만큼을 차지하는 길이의 문을 산에 던진다. 산으로 던진 문은 고점에 닿은 위치에서 쪼개진다고 한다. 이 문제에서는 문이 최대한 많은 개수로 쪼개지길 원한다.
예를 들어 산 데이터가 [3, 2, 3, 2, 1]이고, 문의 길이가 3이라면,
i) 1번째 위치에 놓았을 때: [3, 2, 3, 2, 1] → 쪼개진 문의 개수는 1개
ii) 2번째 위치에 놓았을 때: [3, 2, 3, 2, 1] → 쪼개진 문의 개수는 2개
iii) 3번째 위치에 놓았을 때: [3, 2, 3, 2, 1] → 쪼개진 문의 개수는 1개
위와 같이 쪼개진 문의 개수가 나열된다. ii)의 경우에는 3의 위치에 있는 고점(Peek)이 문을 정확히 반으로 가르기 때문에 쪼개진 문의 개수가 2개가 되는 것이다. i)와 iii)의 경우에는 문이 쪼개지지 않는다.
[ 문제 해설 ]
일단, 길이가 K인 모든 구간을 계산한다고 해보자. 그렇다면, 다음의 공식이 성립한다.
쪼개지는 문의 개수 = 그 구간에 포함된 고점 개수 - 구간의 시작이 고점인지의 여부 - 구간의 끝이 고점인지의 여부
이 문제는 투 포인터(Two Pointers) 알고리즘으로 해결할 수 있다. 투 포인터 알고리즘은 특정 구간안에서의 어떠한 계산을 선형 시간에 해결하기 위해 효과적으로 사용할 수 있다. (정확히 구간이 K칸을 차지할 때만 계산하지 않고, K이하면 계산하도록 하였다. 어차피 이렇게 해도 최적의 해가 나온다.)
[ 정답 코드 ]
for _ in range(int(input())):
n, k = map(int, input().split())
data = list(map(int, input().split()))
# 모든 고점(Peak) 정보를 입력 받기
peaks = set()
for i in range(1, n - 1):
if data[i - 1] < data[i] and data[i] > data[i + 1]:
peaks.add(i)
''' 투 포인터(Two Pointers) 알고리즘 수행 '''
end = 0 # 구간의 끝 인덱스
peak_count = 0 # 구간에 존재하는 고점(Peak)의 개수
result = 0
index = 0
for start in range(n): # 구간의 시작 인덱스를 증가시키며
while end < n and end - start < k:
# 끝 구간이 고점(Peak)이라면 고점의 개수 증가
if end in peaks:
peak_count += 1
# 구간의 시작 혹은 끝이 고점이라면 1씩 빼기
additional = 0
if start in peaks:
additional -= 1
if end in peaks:
additional -= 1
# 가장 많이 쪼개지는 경우를 저장
if result < peak_count + additional:
result = peak_count + additional
index = start
# 시작 구간이 고점(Peak)이라면 고점의 개수 증가
if start in peaks:
peak_count -= 1
end += 1
print(result + 1, index + 1)
C번 문제: http://codeforces.com/contest/1341/problem/C
이 문제는 설명이 길어서 문제 설명은 생략한다.
[ 문제 해설 ]
기본적으로 문제에서는 1부터 n까지의 수를 차례대로 놓아서 순열을 만든다고 한다.
이 문제는 문제 해결의 아이디어만 떠올릴 수 있으면 바로 해결할 수 있다.
문제 해결의 아이디어는 바로 특정한 위치에 i번째 수를 놓게 되면, 오른쪽 끝에 도달할 때까지 1씩 증가시키며 그 다음수를 놓아야 한다는 사실이다. 예를 들어 n = 5일 때, 1을 3번째 자리에 놓게 되면, 무조건 2는 4번째 자리에 두고, 3은 5번째 자리에 두어야 한다.
한 번 순열 [2, 3, 4, 5, 1]을 생각해보자. 이것은 만들 수 있는 순열이다. 1을 5번째 자리에 두고, 2를 1번째 자리에 두면 자연스럽게 [2, 3, 4, 5, 1]가 형성된다. 따라서 "Yes"를 출력하면 된다.
반면에 순열 [1, 3, 2]를 생각해보자. 이것은 만들 수 없는 순열이다. 1을 1번째 자리에 두면, 무조건 [1, 2, 3]만을 만들게 된다. 따라서 "No"를 출력하면 된다.
[ 정답 코드 ]
for _ in range(int(input())):
n = int(input())
data = list(map(int, input().split()))
# 각 수별로 몇 번째 자리에서 등장하는지 기록
my_map = [0] * (n + 1)
for i in range(n):
my_map[data[i]] = i + 1
# n + 1의 위치를 '끝'으로 기록 (왼쪽에서 오면, 여기에서 멈추도록)
blocked = set()
blocked.add(n + 1)
now = 1
result = True
# 수를 1부터 차례대로 확인하며
while now < n:
# 해당 수가 등장한 위치를 가져오기
index = my_map[now]
# 해당 수의 위치를 '끝'으로 기록 (왼쪽에서 오면, 여기에서 멈추도록)
blocked.add(index)
# 더 이상 오른쪽으로 갈 수 없을 때까지 1씩 증가시키며 이동
if index + 1 not in blocked:
# 만약, 오른쪽에 있는 수가 자기보다 1만큼 큰 수가 아니라면
if my_map[now + 1] != index + 1:
result = False
break
now += 1
if result:
print("Yes")
else:
print("No")
'알고리즘 대회' 카테고리의 다른 글
| [코드포스] Codeforces Round #640 (Div. 4) (0) | 2020.05.10 |
|---|---|
| 코드포스(Codeforces) 대회에서 소스코드 해킹(Hack) 방법 (0) | 2020.05.10 |
| [코드포스] Codeforces Round #634 (Div. 3) (0) | 2020.04.23 |
| [코드포스] Codeforces Round #636 (Div. 3) (0) | 2020.04.23 |
| 알고리즘 대회를 위한 C++ 대회 환경 구성하기 (1) | 2018.11.27 |
경기도 재난 지원금 (재난기본소득) 온라인 신청 방법
최근 코로나19(COVID-19)로 인하여 국가적인 재난 상황입니다. 그래서 경기도에서는 2020년 4월부터 7월까지 경기도민들에게 경기도 재난기본소득을 달마다 지급합니다. 신청은 경기지역화폐 사이트에서 진행할 수 있습니다.
▶ 경기지역화폐 사이트: http://www.gmoney.or.kr/
웹 사이트에 방문한 뒤에 [재난기본소득 신청] 버튼을 클릭합니다.

이후에 [온라인 신청]을 눌러서 온라인으로 신청을 진행할 수 있습니다.

약관에 동의합니다.

이후에 경기도 내에서의 시군을 선택합니다. 이후에 카드 번호를 입력해야 하는데, 저는 경기지역화폐카드를 신청해서 수령을 한 상태이기 때문에 해당 카드 번호를 입력했습니다.

이후에 다음과 같이 관할주민센터 동명, 세대주 성명, 세대주와의 관계를 입력해야 합니다. 세대주와의 관계를 입력할 때는 세대주를 기준으로 입력하므로, 세대주가 부모님이라면 본인을 '자녀'라고 넣으셔야 합니다.

조회가 완료되면 다음과 같이 최종적으로 [신청 완료]를 진행하실 수 있습니다.

'기타' 카테고리의 다른 글
| 가상 번호 만들어서 카카오톡 계정 2개 사용하기 (+ 카카오톡 계정 전화번호 교체 방법) (2) | 2020.05.05 |
|---|---|
| 크롬(Chrome) 브라우저가 느리게 동작하거나 인터넷 접속이 안 될 때 해결 방법 모음 (ERR_TIMED_OUT) (3) | 2020.05.05 |
| 윈도우(Windows)에서 make 명령 사용하기 (0) | 2020.04.20 |
| 리눅스(Linux)에서 윈도우(Windows)용 실행 파일을 컴파일하는 방법 (크로스 컴파일) (0) | 2020.04.20 |
| Oracle VM VirtualBox의 게스트(Guest) OS에서 USB 사용하기 (0) | 2020.04.20 |
[코드포스] Codeforces Round #634 (Div. 3)
대회 링크: http://codeforces.com/contest/1335
A번 문제: http://codeforces.com/contest/1335/problem/A
n개의 사탕을 두 사람이 나눠야 한다. 두 사람이 가지는 사탕의 수를 각각 a와 b라고 하자. 이 때 a > 0, b > 0, a > b가 되도록 하는 경우의 수를 출력하는 문제다.
[ 문제 해설 ]
예를 들어, 다음과 같은 n들을 고려해보자.
n = 1일 때는 조건에 맞게 나눌 수 있는 경우가 없다.
n = 2일 때는 조건에 맞게 나눌 수 있는 경우가 없다.
n = 3일 때는 (2, 1)로 나눌 수 있으므로 경우의 수는 1이다.
n = 4일 때는 (3, 1)로 나눌 수 있으므로 경우의 수는 1이다.
n = 5일 때는 (3, 2), (4, 1)로 나눌 수 있으므로 경우의 수는 2다.
n = 6일 때는 (4, 2), (5, 1)로 나눌 수 있으므로 경우의 수는 2다.
n = 7일 때는 (4, 3), (5, 2), (6, 1)로 나눌 수 있으므로 경우의 수는 3이다.
결과적으로 경우의 수는 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, ... 순으로 나열되는 것을 알 수 있다. 따라서 단순히 (n - 1)를 2로 나눈 몫을 출력하면 그것이 정답이다.
[ 정답 코드 ]
for _ in range(int(input())):
n = int(input())
print((n - 1) // 2)
B번 문제: http://codeforces.com/contest/1335/problem/B
길이가 n인 문자열 s를 만드는데, 길이가 a인 모든 부분 문자열들이 b개의 서로 다른 문자들로 구성되도록 하는 문제다. 조건만 만족한다면 길이가 n인 문자열 s를 아무거나 출력해도 된다.
예를 들어 n = 7, a = 5, b = 3일 때를 고려해보자. 이 때는 tleelte가 정답이 될 수 있다.
[ 문제 해설 ]
이 문제는 길이가 a인 부분 문자열 k를 만들어서 순환적으로 출력하면 된다. n = 7, a = 5, b = 3일 때를 생각해보자.
k = ['a', 'b', 'c', 'a', 'a']로 만들게 되면, 예를 순환적으로 출력하면 다음과 같다.
{'a', 'b', 'c', 'a', 'a', 'a', 'b', 'c', 'a', 'a', 'a', 'b', 'c', 'a', 'a', ...}
이렇게 되면, 길이가 5인 어떤 부분 문자열도 3개의 서로 다른 문자로 구성된다. 즉, 문제 해결의 아이디어는 앞에서부터 b만큼은 'a'부터 시작해 차례대로 문자를 담고, 나머지는 그냥 'a'만 나열하도록 문자열 k를 만든다. 이후에 k를 순환적으로 출력하면 정답이다.
[ 정답 코드 ]
for _ in range(int(input())):
n, a, b = map(int, input().split())
k = []
# 앞의 b만큼은 'a'부터 시작해 차례대로 문자를 담기
for i in range(b):
k.append(chr(ord('a') + i))
# 나머지는 그냥 'a'만 나열하도록 문자를 담기
for i in range(a - b):
k.append('a')
# 이제 문자열 k를 순환적으로 출력하면 정답
for i in range(n):
print(k[i % a], end='')
print()
C번 문제: http://codeforces.com/contest/1335/problem/C
n명의 학생을 두 팀으로 나눌 때, 첫 번째 팀은 모두 스킬이 다르고 두 번째 팀은 모두 스킬이 같도록 구성하고자 한다. 단, 두 팀의 인원이 동일해야 한다. 이 때 팀의 가능한 최대 인원 수를 찾는 문제다.
예를 들어 n = 7이고, 학생들의 스킬이 {4, 2, 4, 1, 4, 3, 4}라면 다음과 같이 나누었을 때 각 팀의 인원수가 제일 많다.
첫 번째 팀: {1, 2, 3}
두 번째 팀: {4, 4, 4}
이 때 팀의 인원수는 3이다.
[ 문제 해설 ]
이 문제는 각 스킬의 번호마다 등장 횟수를 체크(count_map)하고, 스킬의 번호가 총 몇 개 있는지 체크(distinct_count)하여 해결할 수 있다.
예를 들어 위의 예시에서는 각 스킬의 번호마다 등장 횟수가 다음과 같다.
1번 스킬: 1명
2번 스킬: 1명
3번 스킬: 1명
4번 스킬: 3명
또한 전체 스킬의 번호가 총 4개가 된다.
이제 각 스킬 번호에 대해서, 그 스킬을 가진 학생들만을 오른쪽 팀에 두는 경우들을 고려하면 된다. 다만, 한 명의 학생은 왼쪽 팀에 넘겨 주어도 괜찮다. 이를 이용해 정답 코드를 작성할 수 있다.
[ 정답 코드 ]
for _ in range(int(input())):
n = int(input())
data = list(map(int, input().split()))
result = 0
count_map = {}
distinct_count = 0
for i in data:
if i not in count_map:
count_map[i] = 1
distinct_count += 1
else:
count_map[i] += 1
# 각 스킬 번호를 확인하며
for (key, value) in count_map.items():
# 해당 스킬을 가진 학생을 두 번째 팀에 두는 경우를 고려하여, 최대 인원 수 찾기
now = max(min(count_map[key], distinct_count - 1), min(count_map[key] - 1, distinct_count))
result = max(result, now)
print(result)
D번 문제: http://codeforces.com/contest/1335/problem/D
이 문제는 전형적인 스도쿠 퍼즐이 무엇인지 알아야 한다. 다음과 같이 이미 완성된 스도쿠 보드가 있다고 해보자.

이 때 안티 스도쿠란, 다음의 조건을 만족하는 보드를 말한다.
1) 각 행에 대하여 최소한 두 개의 원소가 같아야 한다.
2) 각 열에 대하여 최소한 두 개의 원소가 같아야 한다.
3) 모든 3 x 3 블럭(Block)에서 최소한 두 개의 원소가 같아야 한다.
이번 문제는 완성된 스도쿠 보드가 입력되었을 때, 안티 스도쿠를 만들어 출력하는 문제다.
[ 문제 해설 ]
깊게 고민하면 문제 해결 아이디어를 떠올릴 수 있다. 다양한 방법이 있을 수 있겠지만, 저자는 다음과 같이 풀었다. 바로 다음의 9가지 위치에 있는 원소들의 값을 기존의 값이 아닌 다른 아무 값으로나 바꾸면 된다.

따라서 9개의 칸에 있는 각 원소의 값이 1인 경우에는 2로 바꾸고, 1이 아닌 경우에는 1로 바꾸면 된다.
[ 정답 코드 ]
for _ in range(int(input())):
data = []
for i in range(9):
data.append(list(map(int, input())))
# 9가지 위치의 값을 기존의 값이 아닌 다른 값으로 변경
data[0][0] = 2 if data[0][0] == 1 else 1
data[1][3] = 2 if data[1][3] == 1 else 1
data[2][6] = 2 if data[2][6] == 1 else 1
data[3][1] = 2 if data[3][1] == 1 else 1
data[4][4] = 2 if data[4][4] == 1 else 1
data[5][7] = 2 if data[5][7] == 1 else 1
data[6][2] = 2 if data[6][2] == 1 else 1
data[7][5] = 2 if data[7][5] == 1 else 1
data[8][8] = 2 if data[8][8] == 1 else 1
for i in range(9):
print(''.join(map(str, data[i])))'알고리즘 대회' 카테고리의 다른 글
| 코드포스(Codeforces) 대회에서 소스코드 해킹(Hack) 방법 (0) | 2020.05.10 |
|---|---|
| [코드포스] Codeforces Round #637 (Div. 2) (0) | 2020.04.25 |
| [코드포스] Codeforces Round #636 (Div. 3) (0) | 2020.04.23 |
| 알고리즘 대회를 위한 C++ 대회 환경 구성하기 (1) | 2018.11.27 |
| 코드포스(Codeforces) 개요 및 문제 풀어보기 (0) | 2018.11.27 |
