본문 바로가기
카테고리 없음

백준17487-타자 연습

by redcubes 2026. 2. 21.

백준 17487 - 타자 연습

https://www.acmicpc.net/problem/17487

[그림1] QWERTY 키보드 영문 자판 배열. 출처: Wikipedia

문제 요약

QWERTY 키보드에서 영문 문장을 입력할 때, 왼손과 오른손의 키 누름 횟수를 구하는 문제다.

핵심 규칙은 다음과 같다.

키 배정 — Y·G·B 까지가 왼손 영역이고, 그보다 오른쪽(U·H·N 이후)은 오른손 영역이다.

Shift / Space — 이 키들은 양손 어느 쪽으로든 누를 수 있어서, 양손의 누른 횟수 차이를 최소화하는 데 쓴다.

동률 시 왼손 우선 — 차이를 줄인 뒤에도 1이 남으면 왼손이 한 번 더 누른다.

키 분류

QWERTY 배열에서 각 행의 경계를 기준으로 좌우를 나누면 다음과 같다.

왼손 오른손
상단 Q W E R T Y U I O P
중단 A S D F G H J K L
하단 Z X C V B N M

즉, 오른손에 해당하는 소문자 집합은 {u, i, o, p, h, j, k, l, n, m} 이 10개뿐이고, 나머지 16개 영문자는 전부 왼손이다.

풀이 전략

입력 분류

입력의 각 문자를 세 종류로 나눈다.

① 소문자 영문 — 해당 키의 좌/우를 판정해 카운트에 직접 반영한다.

② 대문자 영문 — 실제 키는 소문자와 같으므로 좌/우 판정은 동일하게 하되, Shift를 한 번 눌러야 하므로 "유동 키" 1회를 적립한다.

③ 스페이스 — 좌우 어느 손이든 누를 수 있으므로 "유동 키" 1회를 적립한다.

여기서 유동 키란 Shift와 Space처럼 양손 중 아무 쪽에나 배분 가능한 키 누름 횟수를 말한다.

유동 키 분배

소문자 기준으로 왼손 카운트 $L$, 오른손 카운트 $R$, 유동 키 총 횟수 $J$를 구했다면, $\delta = |L - R|$로 두 손의 차이를 계산한다.

Case 1. $J \ge \delta$ — 유동 키가 차이보다 크거나 같으면, 먼저 $\delta$개로 적은 쪽을 채워 양손을 같게 만든다. 남은 $J - \delta$개는 반으로 나눠 양손에 분배하고, 홀수이면 왼손에 1을 추가한다.

Case 2. $J < \delta$ — 유동 키가 부족하면, 전부 적은 쪽에 몰아 차이를 최대한 줄인다.

비트 연산으로 대소문자 통합

ASCII에서 대문자 A는 65(0x41), 소문자 a는 97(0x61)이다. 둘의 차이는 정확히 32(0x20)로, 6번째 비트 하나만 다르다. 따라서 대문자에 |32 (OR 연산)을 적용하면 해당 비트를 켜서 소문자로 변환할 수 있다. 이미 소문자인 경우에도 해당 비트가 이미 1이므로 값이 변하지 않아 안전하다. 덕분에 좌/우 판정을 소문자 집합 하나로 통일할 수 있다.

코드

import sys

data = sys.stdin.buffer.read().rstrip()
right = set(b'uiophjklnm')

flex = 0          # 유동 키 (Shift, Space)
cnt = [0, 0]      # cnt[0]=왼손, cnt[1]=오른손

for c in data:
    if c == 32:                   # 스페이스 → 유동
        flex += 1
    else:
        if 64 < c < 91:           # 대문자 → Shift 유동 + 소문자 변환
            flex += 1
            c |= 32
        cnt[c in right] += 1      # 소문자 기준 좌/우 카운트

delta = cnt[0] - cnt[1]           # 왼손 - 오른손

if flex >= abs(delta):
    flex -= abs(delta)            # 차이를 메움
    base = max(cnt) + (flex >> 1) # 나머지 반씩 분배
    print(base + (flex & 1), base)  # 홀수면 왼손 +1
else:
    cnt[delta > 0] += flex        # 적은 쪽에 전부 투입
    print(*cnt)

코드 흐름 요약

cnt[c in right]c in rightTrue(=1)이면 오른손, False(=0)이면 왼손을 증가시킨다. 마찬가지로 cnt[delta > 0]에서 delta > 0이면 왼손이 더 크다는 뜻이므로 오른손(인덱스 1)에 유동 키를 투입하고, 반대면 왼손(인덱스 0)에 투입한다.

비트 시프트 flex >> 1은 2로 나눈 몫, flex & 1은 홀짝 판정이다.

예제 검증

예제 1: Konkuk

입력

Konkuk

출력

1 6

소문자로 변환하면 k, o, n, k, u, k — 6글자 전부 오른손 영역이므로 $L=0$, $R=6$이다. 대문자 K를 위한 Shift 1회가 유동 키이므로 $J=1$. $\delta = |0-6| = 6$이고 $J < \delta$이므로, 유동 키 1개를 전부 적은 쪽(왼손)에 투입한다. 결과: 1 6.

예제 2: Hello World

입력

Hello World

출력

7 6

소문자 변환 후: h(R) e(L) l(R) l(R) o(R) w(L) o(R) r(L) l(R) d(L) → $L=4$, $R=6$. 유동 키: 대문자 H → 1, 대문자 W → 1, 스페이스 → 1 = $J=3$. $\delta=|4-6|=2$이고 $J \ge \delta$이므로, 먼저 2개로 차이를 메워 양손 모두 6으로 맞춘다. 남은 $J=1$은 홀수이므로 왼손에 +1 → 7 6.