n번째로 작은 팰린드롬 수를 찾는 놀라운 방법
https://atcoder.jp/contests/abc363/tasks/abc363_d
n이 최대 10^18까지라서 보통의 방법으로는 찾을 수 없다는 것이 느껴진다
그래서 일단 가능한 팰린드롬 수를 차례대로 나열해본다
0,1,2,3,4,5,6,7,8,9 >>> 10개
11,22,33,44,55,66,77,88,99 >>> 9개
101, 111, 121, 131, 141, ... ,191
202,212,222,232,..., 292
303,313,323,..., 393
...
909, 919,...,999 >>9*10 = 90개
1001, 1111, 1221, 1331,..., 1991
...
9009,9119,9229,...,9999 >>> 9*10 = 90개
5자리의 경우도 10001을 보면 1번째 자리에 9가지, 2번째, 4번째 자리에 10가지, 3번째 자리에 10가지 들어가서
9*10*10 = 900개
6자리도 100001을 보면 1번째 자리에 9가지, 2번째, 5번째 자리에 10가지, 3번째, 4번째 자리에 10가지로 900개
...
1자리는 10개
2자리는 9개
3자리는 90개
4자리는 90개
5자리는 900개
6자리는 900개
...
1자리를 제외하고 d자리의 경우 9*(10**((d+1)//2-1)))개 있다는 것을 알 수 있다
1자리는 0~10으로 10개 있으므로, 먼저 n이 10이하이면 n-1을 출력하면 된다
n이 10보다 크다면 2자리 이상의 팰린드롬 수라는 것을 알 수 있고
d = 2부터 1자리씩 올려가면서 n에 10,9,90,90,900,900,...을 차례대로 빼보면서 n이 음수가 될때까지 빼주면
n번째 팰린드롬 수가 몇자리인지 알 수 있다
예를 들어 n = 46이라 한다면.??
d = 2에서 46 - 10 - 9 = 27
d = 3에서 27 - 90 < 0이므로, 46번째 팰린드롬 수는 3자리라는 것을 알 수 있다
d = 1
n -= 10
while n > 0:
d += 1
n -= (9 * 10**((d+1)//2-1))
이게 무슨 뜻이냐면, 46번째 팰린드롬 수를 찾기 위해, 1자리 팰린드롬 수 10개 제외,
2자리 팰린드롬 수 9개 제외하고 3자리 팰린드롬 수 중에 27번째 팰린드롬 수를 찾으면 된다는 뜻이 된다.
3자리 팰린드롬 수 중 27번째 팰린드롬 수는 어떻게 찾을 수 있을까?
101
111
121
131
141
151
...
202
212
222
232
...
898
909
919
...
999
여기서 d = 3일때, 앞 (d+1)//2 = 2자리에 주목해보면 놀랍게도
10, 11, 12, 13, 14, 15, 16, 17, ... ,91,92,93,94,95,96,97,98,99가 순서대로 나열되어 있고
뒤에 1자리는 이 앞 2자리에서 맨 앞 1자리만 그대로 뒤에 붙여주면 만들 수 있다는 것을 알 수 있다
10, 11, 12, 13, 14, 15, 16, 17, ... ,91,92,93,94,95,96,97,98,99에서 27번째 수를 찾으면 된다
그것은 36인데, 놀랍게도 10**((d+1)//2-1)에 27-1을 더해주는 것과 같다.
그리고 36의 앞 1자리를 뒤에 붙여서 363이 n = 46번째 팰린드롬 수이다.
만약 d = 4인 경우를 보면
1001
1111
1221
1331
1441
1551
...
9009
9119
9229
9339
...
9999
역시 앞 (d+1)//2 = 2자리를 보면 놀랍게도
10,11,12,13,14,...,99가 순서대로 되어 있고 여기서 4번째 수를 찾아보면
13 = 10**((d+1)//2-1) + 4-1이며 13을 그대로 거꾸로 해서 뒤로 붙이면 1331이다
따라서 정리하면 n이 10이하이면 n-1이 n번째로 작은 팰린드롬 수
n이 10보다 크다면, n에 10,9,90,90,900,900,...을 차례대로 빼서 n이 음수가 되는 순간 멈춘다
이는 n에 10을 먼저 빼고, d = 2부터 시작해서 9 * 10**((d+1)//2-1)을 빼며 d를 1씩 증가시키는 것과 같다
n이 음수가 되는 순간, 다시 9*10**((d+1)//2-1)을 더해주면 d자리의 n번째 팰린드롬 수를 찾는 것과 같다
d자리의 n번째 팰린드롬 수는 앞 (d+1)//2자리가 10**((d+1)//2-1)+n-1이다.
그리고 d가 짝수라면 앞 (d+1)//2자리 정수를 그대로 거꾸로 붙여주면 된다(13 + 31)
d가 홀수라면 맨 뒤 1자리 제외하고 나머지를 그대로 거꾸로 붙여주면 된다(36 + 3)
n = int(input())
if n <= 10:
print(n-1)
else:
d = 1
n -= 10
while n > 0:
d += 1
n -= (9 * 10**((d+1)//2-1))
n += (9*10**((d+1)//2-1))
answer = 10**((d+1)//2-1) + n-1
if d % 2 == 0:
print(str(answer) + str(answer)[::-1])
else:
print(str(answer) + str(answer)[:-1][::-1])
2. 비슷한 문제
29535번: Геном-палиндром (acmicpc.net)
A,C,G,T만 사용할 수 있는 모든 길이 n인 팰린드롬 문자열을 사전 순서대로 나열할때, k번째 문자열을 찾는 문제
위와 비슷한 원리로 접근하면 된다
길이가 1인 경우는 A,C,G,T로 4가지 있다.
따라서 5번째 문자열부터는 불가능하다.
n,k = map(int,input().split())
answer = ['A','C','G','T']
if n == 1:
if k <= 4:
print(answer[k-1])
else:
print('Impossible')
길이가 2부터는 위에서 배운 원리와 비슷하게 앞 (n+1)//2자리가
AAA
AAC
AAG
AAT
ACA
ACC
...
이런 식으로 순서대로 되기 때문에 이를 먼저 찾고, 팰린드롬이 되도록 뒤에 그대로 붙여주면 된다는 식으로 찾으면 된다
근데 이제 문제는 이렇게 나열한 문자열 중에서 K번째 문자열을 찾아야하는데 이게 좀 어렵더라
예를 들어 앞 3자리만 찾는다고 해보면 가능한 모든 경우는 각 3칸이 A,C,G,T 4가지만 들어갈 수 있으므로
4**3 = 64개 있는데, K가 65이상이면 불가능하다.
따라서 K가 64이하인 경우에만 팰린드롬 문자열이 존재하고
AAA 1
AAC 2
AAG 3
AAT 4
ACA 5
ACC 6
...
여기서 1,2,3,4,5,6,...을 3자리 4진법으로 바꿔보면
001
002
003
010
011
012
...
이렇게 하면 1이면 A, 2이면 C, 3이면 G... 이렇게 접근했는데.. T가 문제더라고...
그래서 ChatGPT한테 물어봤더니 K -= 1로 해서 zero index로 하면 놀랍게도 해결되더라
AAA 0
AAC 1
AAG 2
AAT 3
ACA 4
ACC 5
...
이 상태에서 3자리 4진법으로 바꿔보면
000
001
002
003
010
011
...
이러면 0은 A, 1은 C, 2는 G, 3은 T로 바꾸면 된다
그리고 나서 전체 길이가 홀수라면 맨 뒤 1자리 제외하고 나머지를 거꾸로 그대로 붙이면 될 것이고
전체 길이가 짝수라면 그냥 거꾸로 그대로 붙이면 되는 것
n,k = map(int,input().split())
answer = ['A','C','G','T']
if n == 1:
if k <= 4:
print(answer[k-1])
else:
print('Impossible')
else:
d = (n+1)//2
if k > 4**d:
print('Impossible')
else:
str_list = ['A']*d
chr_list = ['A','C','G','T']
k -= 1
four = []
while k > 0:
p,q = k//4,k%4
four.append(q)
k = p
while len(four) != d:
four.append(0)
for i in range(len(four)):
str_list[d-1-i] = chr_list[four[i]]
answer = ''.join(str_list)
if n % 2 == 0:
print(answer + answer[::-1])
else:
print(answer + answer[:-1][::-1])
'알고리즘 > 애드 혹 알고리즘' 카테고리의 다른 글
n개의 구간 각각에서 어떤 정수들을 골라 합해 0을 만들 수 있는가? (0) | 2024.08.24 |
---|---|
n,m이 매우 클 때 (a1a2a3...an)/(b1b2b3...bm) 분수를 계산하는 마법같은 방법 (0) | 2024.07.25 |
문자열 수를 10진법으로 바꾸는 테크닉 - 배열에서 모든 가능한 순서쌍의 두 수를 이어붙여 만든 수의 합 (0) | 2024.06.11 |
모든 순서쌍의 합의 나머지를 합해야하는데 매 항마다 나머지를 더하면 안되는 문제 (0) | 2024.06.06 |
n으로 나누어 떨어지면서 자릿수의 합도 n으로 나누어 떨어지는 정수 (0) | 2024.06.01 |