원래 특정 수치만큼의 피해가 들어가야할 대상이, 방어력이 있으면 그 수치가 줄어들고 방어력이 높으면 높을수록 더더욱 줄어든다. 게임의 특정 스킬 중에는 방어력이 있는 캐릭터, 몬스터 등의 방어력을 줄여주어 더 많은 딜을 기대하도록 하는 스킬들이 있는 경우가 많다. 이 중 방어력을 감소시키는 스킬은 방어력이 높은 대상이 영향을 더 많이 받을까 혹은 적은 대상이 영향을 더 많이 받을까?
방어력의 공식은 게임마다 다르지만 내가 하는 게임들에서 방어력 공식을 가져와 봤다.
그 게임들의 방어력에 따른 체력의 공식은 대략 다음과 같다:
방어력 > 0 일 때: 실제 피해 = 원래 피해 * 100 / (100 + 방어력),
방어력 <= 0 일 때: 실제 피해 = 원래 피해 * (2 - 100 / (100 - 방어력) )
(방어력이 0 미만으로 내려갈 수 없는 게임도 있다.)
방어력 > 0 인 경우를 생각 해 봤을 때, 체력이 X만큼이라면, 방어력 감소가 적용되기 전의 대미지를 X(1+(방어력/100)) 만큼 맞아도 된다는 것이다. 즉, 방어력 수치는 체력을 (1+방어력/100)만큼 곱해주는 수치라고 보아도 된다. (물론, 방어력을 뚫는 공격 등의 수단이 있는 경우도 있지만, 여기서는 편의상 모든 대미지가 방어력으로 줄어든 후에 들어온다고 생각하자)
이때 방어력 감소가 50, 100 만큼일 때 대미지가 얼마만큼 더 들어가나를 그래프로 그려보았다. (추가 대미지가 50%이면, 대미지가 방어력 감소 전보다 후가 1.5배 더 들어간다.)
일반적인 경우에, 방어력 감소 수치는 보통 방어력을 넘지 않는 경우가 대부분이기 때문이다. 고정수치 방어력 감소의 효율(고정 수치 방어력 감소를 썼을 때 들어가는 추가 대미지 비율)은 상대가 방어력이 높을 경우에 더 낮다.
즉, 고정수치 방어력 감소는 상대가 방어력이 낮을 때 효율적인 수단이다.
이는 사실 일반적으로 "방어력 감소"라는 말을 들었을 때 직관과 잘 안 맞아떨어지는 부분이기도 하다. 제목을 방어력 감소의 함정이라고 지은 것도 이 이유 때문이다.
방어력이 %로 감소한다면 어떨까? 현재 방어력의 25%, 50%를 감소시켜주는 수단이 있으면 어떨까?
이 경우에는 방어력이 높은 경우에 효율이 더 좋음을 알 수 있다.
즉, 퍼센트 방어력 감소는 상대가 방어력이 높을 때 효율적인 수단이다.
내가 요즘 하는 게임은 방어력 감소가 모두 고정수치이고 방어력 수치가 방어력 감소 수단 수치보다 월등히 높으므로, 방어력 감소 스킬은 방어력이 더 낮은 상대에게 쓰는 것이 효율적이라는 것을 알 수 있다.
문제: i번째 정점부터, 다른 모든 정점까지의 거리의 합이 Di인 트리가 있으면 아무거나 출력하고, 없으면 -1을 출력하라. 단, Di는 모두 다르다.
풀이: 먼저 문제의 Special Judge를 생각해봅시다. 모든 i에 대한 Di를 어떻게 구하면 좋을까요? 일단, D1을 1번에서 dfs를 돌린 후에 모든 높이의 합으로 구할 수 있습니다. 이제 다른 모든 Di를 구해야 하는데, 하나의 Di를 구하면 다른 Di를 구할 수 있습니다. 이는 다음의 정리를 이용합니다.
정리. T(u)를 u까지의 거리가 v까지의 거리가 더 짧은 정점의 집합이라고 정의하고, T(v)에 대해서도 마찬가지로 정의하자. u와 v가 간선으로 연결되어 있을 때, Du+|T(u)| = Dv+|T(v)|이다.
증명. x를 T(u)의 원소라고 합시다. x와 v의 경로는 u를 지나므로, d(x, u) + d(u, v) = d(x, v)입니다. 마찬가지로, y를 T(v)의 원소라고 하면, y와 u의 경로는 v를 지나므로, d(y, v) + d(v, u) = d(y, u)입니다. d(u, v) = 1이므로, |T(u)|개의 수에 대해서는, d(x, u) + 1 = d(x, v)이고, |T(v)|개의 수에 대해서는, d(x, u) = d(x, v) + 1입니다. 모든 x에 대해 식을 전부 더해줄 경우에, Du + |T(u)| = Dv + |T(v)|가 나오게 됩니다.
그렇기 때문에, 우리는 어떤 노드 u와 그 부모 p에 대해서, Du + |T(u)| = Dp + |T(p)| = Dp + N - |T(u)|. 정리하면, Dp = Du + 2|T(u)| - N 이라는 사실을 얻어 낼 수 있고, 루트 부터 시작해서, 모든 트리의 정점들의 D값을 알 수 있습니다. 이제 복구를 시작해 봅시다. 일반성을 잃지 않고 D1 < D2 < ... < DN 이라고 가정합시다. 제가 처음으로 접근한 방법은 centroid를 먼저 루트로 잡는 것이었습니다. centroid는 Di 가 제일 작은 수이기 때문에 D1을 루트로 잡을 수 있습니다. centroid에서 루트를 제외한 subtree의 크기는 모두 N/2이하이기 때문에, 자식의 Di가 부모의 Di보다 무조건 작음을 알 수 있습니다. 그래서 두번째로 큰 수는, 루트의 자식이 되어야 하고, subtree의 크기를 결정할 수 있습니다. 세번째로 큰 수 까지는 어떻게 결정할 수 있는데, 네번째로 큰 수가 복구가 쉽게 되지 않습니다. 왜냐하면 트리에 붙을 수 있는 방법이 두가지 이상이 되어버리기 때문입니다.
그래서 이 아이디어를 그대로 이용하여, 리프부터 복구를 시작했습니다. x를 N부터 1까지 차례대로 내려오면서 x의 부모노드를 구합시다. (1번 정점은 centroid이고, 루트입니다.) x번째 노드의 D값이 Dx이면, 그 부모 노드 p에 대해서는, Dp = Dx + 2|T(x)|-N 입니다. D값이 모두 다르기 때문에, p가 유일하게 정해집니다. |T(x)|는, 정점 x보다 Dx 큰값에 대해 부모가 모두 정해져 있기 때문에 바뀌지 않습니다. 이렇게 p를 정하면 p가 x의 부모로 정해지게 됩니다. (구현시에는, T(p)의 값을 T(p) + T(x)로 업데이트 합니다.)
그래서, 우리는 이 방식으로 부모를 계속 구하고, 트리를 복구한 후에, 복구 한 트리가 실제로 D값을 만족하는지를 판단하면 됩니다.
문제: 간선 하나를 잘라서 크기 x인 연결성분을 만들 수 있는지에 대한 여부가 1이상 n이하인 x에 대해 주어진다. 이 조건을 만족하는 트리가 있으면 아무거나 하나 출력하고, 없으면 -1을 출력하여라.
풀이: 또 다른 트리 복구 문제입니다. 몇몇 자명한 조건들을 살펴봅시다. 일단 간선 하나를 끊었을 때, 하나의 크기가 x이면 다른 하나의 크기는 n-x입니다. 그래서 si가 0인데, sn-i가 1인 경우에 (혹은 sn이 1인 경우에) 우리는 트리가 불가능하다고 말해주면 됩니다. 다른 자명하지만 중요한 조건을 하나 살펴봅시다. 트리에는 항상 리프가 존재하므로, 리프와 부모를 잇는 간선을 자른 경우에는 크기 1인 연결성분이 생기고, s1은 무조건 1이어야 합니다. 이렇게 s에 대한 기본적인 체크를 한 이후에, 우리는 s를 만들어 볼 것입니다. 또, 트리의 루트는 아무거나 잡으면 되기 때문에 centroid를 잡을 것 입니다. centroid를 트리의 루트로 잡으면 루트를 제외한 subtree의 크기는 모두 N/2이하입니다. 그래서 우리는 주어진 1이상 N/2이하의 숫자들에 대해서, subtree의 크기가 정확히 주어진 수가 되도록 만들면 됩니다. 만드는 한 가지의 방법은 다음과 같습니다. 주어진 수들을 1 < a1 < a2 < ... < aK 라고 합시다. 일단, 가장 큰 수인 aK가 subtree의 크기인 노드는 바로 자식이어야 될 것 입니다. 나머지 N-aK의 노드들은 어떻게 할까요? 우리는 루트에다가 N-aK개의 리프를 붙일 것 입니다. 이 정점들의 subtree의 크기는 1이고, 다른 풀이에 영향을 주지 않습니다. 이제 aK가 subtree의 크기인 노드에서, aK-1이 subtree의 크기인 노드를 만들어야 합니다. 나머지 선택지는 없으니, 자식으로 붙여주고, 나머지 aK-aK-1개의 노드들을 리프로 붙여줍시다. 이와 같은 방법으로 만들면, 문제의 조건을 만족하는 경로 하나가 있고, 리프가 마구 붙은 형태의 트리를 만들 수 있습니다.
문제: 로봇팔은 원점에서 길이 d1, d2, ..., dm의 막대가 붙어있는 형태이다. (d는 양의 정수이다.) 각 막대 사이에는 관절이 있어서, 각 막대는 상하좌우중 아무 방향으로나 움직일 수 있다. 로봇 팔의 가장 마지막 부분이 주어진 좌표평면 N개의 점을 지나도록 로봇 팔을 구성할 수 있는가? (m ~ 40, N ~ 1000, d ~ 10^9)
풀이: 일단 진정하고 문제를 1차원에서 먼저 풀어봅시다. 로봇 팔은 L과 R밖에 없으며, 모든 점은 다 x축 위에 있습니다.
이 문제는 결국 +-d1+-d2+-d3+-...+-dm 으로 정해진 수를 모두 만들 수 있게 하라는 질문과 같습니다. -d1-d2-d3-...-dm 부터 생각해 보았을 때, di의 부호를 -에서 +로 바꾸는 것은 결국엔 2di를 더하는 것과 같습니다. 결론적으로, (+-1)+-1+-2+-4+-...+-2k와 같이 숫자를 정하게 될 경우에, -d1-d2-d3-...-dm 으로 부터, 정해진 위치인 x까지 더해야 할 수 x+d1+d2+...+dm 을 2진법으로 표현하는 문제가 됩니다. 홀짝에 따라 처음 1은 포함되어야 할 수도 있고 아닐수도 있습니다. 이제 이 문제를 2차원에서 풀어야 합니다. 이것은 좌표평면을 45도 돌리면 가능합니다. L, R, D, U가 (x+y, x-y) -> (x+y+-2d, x-y+-2d) 에 대응되기 때문에, 문제를 x+y축과 x-y축에 대해 따로따로 풀어주면 됩니다.
문제: Nim 게임을 하는데, i번째 무더기에는 처음에 Ai개의 돌이 있고, i번째 무더기에 X개의 돌이 있을 때 최소 1개에서 최대 X/Ki (나머지는 버림)개 사이의 갯수에 돌만 가져갈 수 있습니다. 서로 번갈아가면서 하나의 무더기에서 몇개의 돌을 번갈아가면서 가져가고, 더 이상 돌을 가져갈 수 없는 사람이 진다고 했을 때, 첫째 사람이 이기는지, 둘째 사람이 이기는지를 구하시오.
풀이: 이 문제는 그냥 Nim 게임(모든 K가 1인 경우)를 푼다고 하면, Impartial Game에서의 Sprague-Grundy Theorem를 사용합니다. 이에 관한 정보는 위키피디아 게시글에서 얻을 수 있습니다.
요약하자면 Impartial Game이란 누가 플레이하냐에 관계 없이, 현재 상태로, 바꿀 수 있는 다음 상태가 정해지며 (첫째 사람 혹은 둘째 사람만 가져갈 수 있는 돌이라는건 존재하지 않습니다.) 유한한 게임입니다. (게임이 무한히 진행될 수 없습니다.) Normal play condition 이라는 것은, 더 이상 일을 계속할 수 없는 사람이 진다는 것을 의미합니다. 여기서 상태라고 함은, 이 게임에서는 각 무더기에 있는 돌의 갯수가 됩니다.
여기에서 우리는 Nimber(혹은 Grundy Number)라는 것을 정의합니다. 이 Nimber는 재귀적으로 정의 되는데, "갈 수 있는 상태의 Nimber를 모두 모았을 때, 등장하지 않는 가장 작은 0 이상의 정수." 라고 정의합니다.
예를 들어, K=4인 상황을 가정합시다. 이 경우에 X = 0, 1, 2, 3인 경우에는 돌을 더 이상 가져갈 수 없기 때문에 (X/K 가 1 미만이기 때문에) 갈 수 있는 상태가 존재하지 않기 때문에, 어떤 정수도 등장하지 않고 Nimber는 0이 됩니다. X = 4인 경우에는, X=3인 상태로만 갈 수 있습니다. 이 때의 Nimber는 0이고, 1은 존재하지 않기 때문에, X=4일때 Nimber는 1입니다. 마찬가지로 X=5일때는 0, X=6일때는 1, X=7일때는 0, X=8일 때는, 1개 이상 2개 이하의 돌을 가져갈 수 있고, X=6, 7인 상태로 갈 수 있고, Nimber를 모두 모으면 0, 1이기 때문에 2가 등장하지 않고, X=8일때의 Nimber는 2입니다. X=9일때의 Nimber는 X=7, 8일때의 Nimber를 모아 보면 0과 2인데, 1이 등장하지 않는 가장 작은 수 이므로 Nimber가 1입니다.
이렇게 K=4인 경우에 Nimber를 만들어 보면 다음과 같은 표가 나옵니다.
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0
0
0
0
1
0
1
0
2
1
0
2
3
1
0
2
4
3
이 Nimber에는 다음과 같은 성질이 있습니다. Nimber가 0이면 자신이 패배하고, Nimber가 0이 아니면 자신이 승리합니다. 이유는, Nimber가 0이 아니면, 0이 다음 상태라는 것을 의미하고, 다음 사람의 턴을 Nimber가 0인 상태로 만들 수 있습니다. 이렇게 다음 사람의 Nimber를 0으로 계속 만들 수 있고, Nimber가 0이 아니면 갈 수 있는 상태가 있다는 것을 의미하기 때문에 패배하지 않습니다. 그래서 4, 6, 8, 9, 11, 12, ... 이면 K=4일때 무더기가 하나일 때 처음 사람이 이깁니다.
그럼 여러개의 게임을 동시에 진행하는 경우에는 어떨까요? 우리는 Sprague-Grundy Theory를 쓰게 됩니다. 여러개의 게임을 동시에 진행하는 경우의 Nimber는 모든 subgame의 Nimber를 XOR한 값입니다. 증명은 위키백과 문서를 참고해 주시기 바랍니다.
이제 우리는 이 멋진 정리의 도움을 받아, 각 수의 Nimber를 구하는 것으로 일이 줄었습니다. 각 수의 Nimber를 구하는 방법은 어떻게 될까요? 위의 표에서 규칙성을 찾으면 다음과 같은 정리를 증명할 수 있습니다.
정리: nimber(X-X/K)... nimber(X)는 0부터 X/K까지의 X/K+1개의 수가 모두 존재한다.
증명: 강한 수학적 귀납법을 사용합니다.
base case: X<K, nimber(X)는 0이고, 자명합니다.
inductive case:
1. X가 K를 정확히 나눌 때, X-X/K는 (X-1) - (X-1)/K과 같으므로, X가 갈 수 있는 상태인 nimber(X-X/K)...nimber(X-1)를 모두 모으면 0부터 (X-1)/K = X/K-1 까지 이고, nimber(X)는 X/K 이므로, 성립합니다.
2. X가 K를 정확히 나누지 않을 때, X 가 갈 수 있는 상태인 nimber(X - X/K)...nimber(X-1)을 모두 모으면, 0부터 X/K = (X-1)/K 에서, nimber(X-1 - (X-1)/K)가 빠진 형태가 됩니다. 여기서 nimber의 정의에 의해서 nimber(X) = nimber(X-1 - (X-1)/K)가 되어서, 성립하게 됩니다.
우리는 이 증명을 하면서 유용한 정보를 얻었습니다. X가 K를 정확히 나누면 nimber(X) = X/K이고, 아니면 nimber(X) = nimber(X-X/K-1)이 됩니다.
이것을 그대로 구현하면 시간이 오래 걸리게 됩니다. 가령이면, K=A/2+1 정도의 경우를 생각 해 봅시다. X/K = 1이고, 계속 2씩 숫자를 빼게 되면, A/4번 정도 뺄셈을 하게 됩니다. 이렇게 되면 O(A)의 시간이 걸리게 되겠죠. 그래서 우리는 여기서 시간복잡도를 줄일 아이디어를 얻을 수 있습니다. 바로 계속 2씩 숫자를 뺀다는 부분은, X/K 가 작은 경우에, 같은 숫자를 빼는 경우가 많다는 것입니다. X/K 가 바뀌려면, X%K 만큼을 빼야하고, 이것을 빼는 동안에는 X/K의 배수로 한번에 빼 줄수가 있습니다.
이 경우에 숫자는 X/K가 적어도 1씩 줄고, K가 적어도 1씩 줄게 됩니다. (X/K - 1)(K-1) = XK - X/K - K + 1이므로, 숫자는 X/K + K 만큼은 적어도 줄게 되고, 산술기하평균 부등식에 의해서, 숫자가 적어도 sqrt(X)씩 줄게 되어서, 시간복잡도가 O(sqrt(A)) 가 나오게 됩니다.
우리는 어떤 무더기 하나의 Nimber를 sqrt(A)에 구할 수 있습니다! 이 Nimber를 모두 xor하면 답이 나오게 됩니다. 총 시간복잡도는 O(nsqrt(A)) 가 됩니다.
문제: 0과 1로 이루어진 문자열 S에서, 어떤 부분구간을 flip한다는 것은, 그 구간의 부분문자열의 문자를 0을 1로, 1을 0으로 바꾼 다는 것을 의미합니다. 길이 K이상의 구간만을 flip하여 모든 문자를 0으로 만들 수 있다고 할 때, 가능한 K의 최댓값을 구하시오. (동시에 K는 N이하)
풀이: [1, X] 구간을 flip하고 [1, X+1] 구간을 flip하면 X+1번째 문자만 flip할 수 있습니다. 즉, X가 K이상일 경우에는, 숫자를 하나씩 flip하는게 마찬가지라는 것 입니다. 그래서 왼쪽에서 세어서 K+1번째 이후 문자의 상태는 모두를 0으로 바꿀때 상관이 없습니다. 오른쪽에서도 마찬가지의 논리가 성립합니다.
그래서 왼쪽에서 세어도, 오른쪽에서 세어도 K+1번째 이내인 수들에 대하여, 이 문자들이 모두 같은지 (같으면 전체를 flip할 수 있습니다.)를 판단해 주면 됩니다. 다른 경우에는, 이 문자들은 길이 K의 구간을 어떻게 잡아도 모두를 한번에 뒤집을 수 밖에 없기 때문에, 전부를 0으로 만드는게 불가능 합니다.
이런 K를 문자열의 가운데 부터 시작해서 같은 구간이 어디까지를 세어 줄 경우에 O(|S|)의 시간복잡도로 풀 수 있습니다.
문제: 두 정수의 쌍 (a, b) 중에서, 1 ≤ a, b ≤ N 이면서, a를 b로 나눈 나머지가 K 이상인 것의 갯수를 세시오.
풀이: mod를 규칙적으로 보기 위해서는 b가 고정되어있으면 편하기 때문에, b를 고정시켜 놓습니다. b를 고정시킨 경우에는 어떤 수를 b로 나눈 나머지는 0, 1, 2, 3, ..., b-1 까지 갔다가, 다시 0부터 시작해서 1씩 증가하고, b-1 다음엔 0으로 돌아온다는 사실을 알 수 있습니다. 그래서 이렇게 돌아가는 수열 중에서 K 이상인 것의 갯수를 세는 것은, 반복되는 부분인 0, 1, 2, 3, ..., b-1이 몇번 반복 되는가와, 가장 마지막에 어떤식으로 수열이 끝나는지를 알면 알 수 있습니다. 자세한 수식은 코드의 수식을 보면 알 수 있습니다.
문제: 길이 N의 두 수열 a와 b가 주어진다. 모든 가능한 i와 j에 대해서 ai+bj의 합 N2개를 모두 XOR 한 값을 구하시오.
풀이: 모든 합을 구해서 XOR하는것은 구하기 때문에, XOR을 구할때 비트별로 볼 것 입니다. 우리가 어떤 특정한 2B 에 해당하는 비트를 볼 때, 그 위에 있는 비트는 영향을 주지 않습니다. 중요한 것은 우리가 구하려는 비트(2B)에 있는 값과, 그보다 하위 비트(작은 두 수를 더해서 2B의 비트에 영향을 줄 수 있음)에 있는 값 입니다. 한 비트에 대해서 XOR은 1이 홀수개 있는지 짝수개 있는지 묻는 연산이기 때문에, 우리는 결국에는 해당하는 비트의 1의 홀짝성을 알려주면 됩니다. 즉, 이 비트에 영향을 홀수번 주었나, 짝수번 주었나를 묻는 문제입니다.
일단 우리가 구하려는 비트 부터 살펴봅시다. 한 원소의 구하려는 비트가 1이라는 것은, 합 N개의 비트에 영향을 주었다는 의미입니다. 그래서 우리는 수열에 해당하는 비트의 원소가 몇개 있는지를 살펴보면 됩니다. 이제 우리가 구하려는 비트에 영향을 끼치는 갯수는 구했습니다.
이 후에는, 우리가 구하려는 비트보다 하위 비트에 영향을 주는 갯수만 세면 됩니다.
그래서 우리는 이 문제를 비트마다 구하는 문제로 바꾸면, 각 수열의 원소는 2B 보다 작고, 2B에 해당하는 비트가 0인지 1인지를 구하는 문제가 됩니다. 2B에 해당하는 비트가 0인지 1인지 구하는 문제이지만, 어떤 수열의 두 원소의 합은 2B의 두배를 넘지 못하기 때문에, 결국 합해서 2B보다 큰 합의 갯수를 세는 문제로 바뀝니다. 이 문제는 sliding window를 사용하거나, 아니면 binary search를 사용해서 간단하게 구할 수 있습니다. 어떤 한 원소가 x이면, N-x보다 크거나 같은 수의 갯수를 std::lower_bound 등을 이용해 간단하게 세어 줄 수 있습니다. 제 구현은 후자를 사용했고 코드는 다음과 같습니다.
문제: A와 B가 주어졌을 때, 흰색 connected component의 갯수가 A개, 검정색 connected component의 갯수가 B개인 격자를 아무거나 찾아서 출력하시오.
풀이: 이것은 저의 풀이고, 다양한 풀이가 존재할 수 있습니다.
A가 1인 경우를 풀려고 해 봅시다. 일단 B가 0이면, 흰색으로 전체를 덮으면 됩니다. 여기서 B가 매우 크다고 해 봅시다. 그러면 흰색 component에 검정색 component 여러개를 끼워넣는 방법이 되어야 합니다. 그래서 우리는 흰색 component에 검정색 component를 서로 영향을 주지 않게 흰색끼리 연결되어있는 상태로 추가할 수 있습니다. 마치 수박에 있는 수박씨 처럼 흰색에다가 검정색으로 점을 서로 띄어서 찍어주면 됩니다. 이럴 경우에는 A가 1인 경우를 풀 수 있습니다. B를 추가하는게 점을 찍는것 입니다.
마찬가지로, 하얀색을 한 칸 추가할 수 있는 환경을 만드려면, 검정색으로 전체를 덮으면 됩니다.
저는 하얀색과 검정색을 반 반 나눠서 칠한 후 (A = B = 1), 검정색과 하얀색으로 점을 B-1, A-1개 각각 찍는데 서로 최소 2씩 거리가 나게 찍었습니다. 이렇게 찍은 경우에는 하얀색도 검정색도 배경은 연결되어 있다는 것을 알 수 있습니다.
이 문제는 다양한 풀이가 존재하고, 우리가 만들어야하는 격자의 크기가 작을 경우에 만들 수 있는가? 를 물어보는 문제도 재밌을 것 같습니다.
티스토리 블로그에도 달고 싶은데 지원을 안하네요... SSL 인증서 좀 지원해주면 좋으려만
좀 더 자세한 얘기는 호스팅을 bluehost에서 linode로 옮겼습니다. 처음에 서버 세팅에 관한 지식이 없을때 php로 사이트를 뚝딱뚝딱 만든것도 있고, 가격이 월 10$라서 샀었는데, 지금은 linode하나 올려서 쓰는게 좀 더 마음이 편하네요. 가장 싼 플랜이 한 달 5$ 에요. 역시 루트권한 있으니까 마음이 편해요.
예전에는 블루호스트가 인증서 적용하는게 유료라서 못 달았는데, 지금은 letsencrypt로 인증서를 받아서 적용했습니다.
호스팅이 이것저것 난잡해지기도 해서, 아예 flask기반으로 웹을 재구성 하고 있는데 아직 옮기지 못한게 있네요.
하나는 Musicplayer인데, Youtube Red를 쓰니까 별로 쓸 일이 없어지는것 같기도 하네요.
다른건, 사람들에게 링크를 줄 때 잡다하게 파일들을 올리고 그걸 주는 방식으로 문제를 만들었는데, 이것들은 지금 살려둬야 할지 말아야 할지 모르겠는 링크도 많아서 일단은 안 살려 뒀습니다...
사실 최근에 트래픽 부담이 꽤나 있는데 linode도 한달정도 써보고 트래픽 부담이 있으면 여기만 따로 옮기는 방식을 쓰든가 여러가지 조치를 취해야 할 것 같네요.