devonnuri.wiki

2025년 12월 1주차 TIL

입력 2025-12-02 10:33:07

수정 2025-12-07 07:39:51

상위 항목 : Today I Learned

TOC

  1. 251202
    1. BOJ 2342 : Dance Dance Revolution (Gold 3)
    2. BOJ 2170 : 선 긋기 (Gold 5)
    3. BOJ 20412 : 추첨상 사수 대작전! (Hard) (Gold 2)
  2. 241204
    1. BOJ 13975 : 파일 합치기 3 (Gold 4)
  3. 251205
    1. BOJ 11401 : 이항 계수 3 (Gold 1)
    2. BOJ 1069 : 집으로 (Gold 3)
  4. 251206
    1. BOJ 1562 : 계단 수 (Gold 3)
    2. BOJ 2162 : 선분 그룹 (Platinum 5)
  5. 251207
    1. BOJ 4195 : 친구 네트워크 (Gold 2)
    2. BOJ 14890 : 경사로 (Gold 3)
    3. BOJ 13977 : 이항 계수와 쿼리 (Platinum 5)

251202

BOJ 2342 : Dance Dance Revolution (Gold 3)

  • DP 문제인데 애먹어서 못 풀다가 이제 풀었다.

  • 번째 위치를 밟을 때, 그리고 마지막 두 발의 위치가 일 때의 최소의 총 힘이라고 정의하자. 그러면 다음과 같은 transition rule을 생각할 수 있다.

    • 여기서 는 허용 상태 집합이며 다음과 같이 정의된다.

  • 상태는 직전 에만 영향을 받으므로 모두 저장할 필요 없다.

from math import inf

l = [int(x) for x in input().split()][:-1]
dp = [[inf] * 5 for i in range(5)]
dp[0][0] = 0

def cost(x, y):
  if x == 0:
    return 2
  if x == y:
    return 1
  return 3 + ((x-y)%2 == 0)

for x in l:
  tmp = [[inf] * 5 for i in range(5)]
  for j in range(5):
    for k in range(5):
      if j == k and j != 0:
        continue
      tmp[x][j] = min(tmp[x][j], dp[k][j] + cost(k, x))
      tmp[j][x] = min(tmp[j][x], dp[j][k] + cost(k, x))
  dp=tmp

print(min([min(x) for x in dp]))

BOJ 2170 : 선 긋기 (Gold 5)

  • 스위핑 기초다. 이벤트 리스트가 포인트이다.
#include <bits/stdc++.h>

using namespace std;

using vi = vector<int>;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  vector<pi> events;
  int n; cin >> n;

  for (int i=0;i<n;i++) {
    int l, r; cin >> l >> r;
    events.emplace_back(l, 1);
    events.emplace_back(r, -1);
  }

  sort(events.begin(), events.end());

  int cur = 0, start = 0, res = 0;
  for (auto [p, e] : events) {
    if (e == 1 && cur == 0) start = p;
    cur += e;
    if (e == -1 && cur == 0) res += p - start;
  }
  cout << res;

  return 0;
}

BOJ 20412 : 추첨상 사수 대작전! (Hard) (Gold 2)

이 두 식을 빼면,

  1. 일때,

    이므로, 해 는 무수히 많다. 으로 잡을 수 있다.

  2. 일때,

    페르마의 소정리에 의해,

    는 첫 식에 대입해서 구하면 된다.

m, s, x1, x2 = map(int, input().split())
if s==x1:
  print(1, 0)
else:
  a = (x1-x2)*pow((s-x1)%m,-1,m)%m
  c = (x1-a*s)%m
  print(a,c)

241204

BOJ 13975 : 파일 합치기 3 (Gold 4)

  • 허프만 코딩 문제다.

  • 허프만 코딩 증명은 다음과 같이 하면 된다. 이거 하면서 교환 논법 감 잡은듯.

    • 다음을 증명한다.

      주어진 가중치 , , 에 대해 항상 가장 작은 두 개를 합쳐 가중치 인 새 노드를 만들고, 이걸 반복해서 만든 이진트리가 전체 가중 경로 길이 를 최소로 한다.

    • 이를 위해 다음 두 Lemma를 증명한다.

      1. 가장 작은 두 가중치는 최적 트리에서 형제(같은 부모)로 둘 수 있다.
        • 얘는 교환 논법으로 증명할 수 있는데, 최적 트리의 가장 깊은 리프가 가중치가 가장 작지 않을 경우, 그렇게 바꾼 뒤 비용 변화가 얼마가 될지 구하면 된다.
      2. 두 최소 가중치를 하나로 합치면 부분문제도 최적이다.
        • 이 부분은 직관적으로 알 수 있다.
    • Lemma를 모두 증명한 뒤엔 수학적 귀납법으로 마무리하면 된다.

from heapq import heapify, heappop, heappush

t=int(input())
for _ in range(t):
  n=int(input())
  q=[int(x) for x in input().split()]
  heapify(q)
  res=0
  while len(q)>1:
    a,b=heappop(q),heappop(q)
    heappush(q, a+b)
    res+=a+b
  print(res)

251205

BOJ 11401 : 이항 계수 3 (Gold 1)

  • 단계별로 풀어보기에서 힌트를 줘서 풀었다.

  • 이항계수를 구하면 다음과 같다. 역원은 페르마의 소정리로 구하면 된다.

n,k=map(int,input().split())
res=1
p=int(1e9)+7
for i in range(n-k+1,n+1):
  res=(res*i)%p
k_fac=1
for i in range(1,k+1):
  k_fac=(k_fac*i)%p
res=(res*pow(k_fac,p-2,p))%p
print(res)

BOJ 1069 : 집으로 (Gold 3)

  • Exhaustive하게 케이스 분류를 하자면, ()일 때, 일 때를 나누고, 그 안에서 0번 점프, 1번 점프, ...를 나누어서 최소 시간 케이스를 고르면 된다.
  • 일 때에는 0번 점프, 1번 점프, 2번 점프 중 하나가 최적이고, 그 이상 점프하면 2번 점프보다 항상 비효율적이다.
  • 일 때에는 0번 점프, 번 점프, 점프 중 하나가 최적이다. ()
x,y,d,t=map(int,input().split())
l=(x*x+y*y)**.5
if d>l:
  print(min(t+d-l,l,2*t))
else:
  n=int(l/d)
  print(min(n*t+l-n*d,(n+1)*t,l))

251206

BOJ 1562 : 계단 수 (Gold 3)

  • 을 마지막(번째) 자리수, 를 0-9 중 등장한 수의 집합(비트셋)이라고 할 때, 를 해당 조건을 만족하는 계단 수의 개수라고 정의하자.

  • 그러면 다음과 같은 transition rule이 생각난다.

    • 이때 이고, 범위에 벗어나는 경우 0으로 가정하면 된다.
  • 연산자 우선순위 때문에 한참 애먹었다. 까불지 말고 괄호 많이 치자.

#include <bits/stdc++.h>

using namespace std;

// dp[last digit][bitset of 0-9]
int dp[10][1024] = {0, };
int cur[10][1024] = {0, };

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n; cin >> n;

  for (int l=1;l<10;l++)
    dp[l][1<<l] = 1;

  for (int i=1;i<n;i++) {
    memset(cur, 0, sizeof(cur));
    for (int l=0;l<10;l++) {
      for (int b=0;b<1<<10;b++) {
        if ((b&(1<<l)) == 0) continue;
        if (l>0) {
          cur[l][b] = (cur[l][b] + dp[l-1][b]) % 1000000000;
          cur[l][b] = (cur[l][b] + dp[l-1][b&~(1 << l)]) % 1000000000;
        }
        if (l<9) {
          cur[l][b] = (cur[l][b] + dp[l+1][b]) % 1000000000;
          cur[l][b] = (cur[l][b] + dp[l+1][b&~(1 << l)]) % 1000000000;
        }
      }
    }
    memcpy(dp, cur, sizeof(cur));
  }

  int res = 0;
  for (int i=0;i<10;i++) {
    res = (res + dp[i][1023]) % 1000000000;
  }

  cout << res;

  return 0;
}

BOJ 2162 : 선분 그룹 (Platinum 5)

  • 선분 교차 + DSU 하면 된다.
  • 선분 교차 알고리즘에 실수가 많아서 많이 틀렸다. 사고과정(아니면 알고리즘 자체)를 익숙하게 만들어야 한다.
#include <bits/stdc++.h>

using namespace std;

using vi = vector<int>;
using pi = pair<int,int>;

struct DisjointSet {
  vi parent, rank;

  DisjointSet(int n) {
    parent.resize(n);
    rank.resize(n,1);
    for (int i=0;i<n;i++)
      parent[i]=i;
  }
  
  int find(int u) {
    if (u==parent[u]) return u;
    return parent[u]=find(parent[u]);
  }

  int merge(int u, int v) {
    u=find(u), v=find(v);
    if (u == v) return rank[u];
    if (rank[u]>rank[v]) swap(u,v);
    parent[u] = v;
    rank[v] += rank[u];
    return rank[v];
  }
};

int ccw(int x1, int y1, int x2, int y2, int x3, int y3) {
  int res = (x2-x1)*(y3-y1) - (x3-x1)*(y2-y1);
  if (res > 0) return 1;
  if (res < 0) return -1;
  return 0;
}

bool intersect(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
  int ccw1 = ccw(x1, y1, x2, y2, x3, y3) * ccw(x1, y1, x2, y2, x4, y4);
  int ccw2 = ccw(x3, y3, x4, y4, x1, y1) * ccw(x3, y3, x4, y4, x2, y2);
  if (ccw1 == 0 && ccw2 == 0) {
    pi p1 = {x1, y1};
    pi p2 = {x2, y2};
    pi p3 = {x3, y3};
    pi p4 = {x4, y4};

    if (p1 > p2) swap(p1, p2);
    if (p3 > p4) swap(p3, p4);
    return !(p2 < p3 || p4 < p1);
  }
  return ccw1 <= 0 && ccw2 <= 0;
}

int coord[3000][4];

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n; cin >> n;
  for (int i=0;i<n;i++)
    for (int j=0;j<4;j++)
      cin >> coord[i][j];
  
  DisjointSet ds(n);
  int m = 1;
  for (int i=0;i<n;i++)
    for (int j=i+1;j<n;j++)
      if (intersect(coord[i][0], coord[i][1], coord[i][2], coord[i][3], coord[j][0], coord[j][1], coord[j][2], coord[j][3]))
        m = max(m, ds.merge(i, j));

  set<int> si;
  for (int i=0;i<n;i++)
    si.insert(ds.find(i));

  cout << si.size() << '\n' << m;

  return 0;
}

251207

BOJ 4195 : 친구 네트워크 (Gold 2)

  • Map + DSU로 풀면 된다.
#include <bits/stdc++.h>

using namespace std;

using vi = vector<int>;

struct DisjointSet {
  vi parent, rank;

  DisjointSet(int n) {
    parent.resize(n);
    rank.resize(n,1);
    for (int i=0;i<n;i++)
      parent[i]=i;
  }
  
  int find(int u) {
    if (u==parent[u]) return u;
    return parent[u]=find(parent[u]);
  }

  int merge(int u, int v) {
    u=find(u), v=find(v);
    if (u == v) return rank[u];
    if (rank[u]>rank[v]) swap(u,v);
    parent[u] = v;
    rank[v] += rank[u];
    return rank[v];
  }
};

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int T; cin >> T;
  while (T--) {
    DisjointSet ds(200000);
    map<string,int> ms;
    int f; cin >> f;
    for (int i=0;i<f;i++) {
      string a, b;
      cin >> a >> b;
      if (!ms.count(a)) ms[a] = ms.size();
      if (!ms.count(b)) ms[b] = ms.size();
      ds.merge(ms[a], ms[b]);
      cout << ds.rank[ds.find(ms[a])] << '\n';
    }
  }

  return 0;
}

BOJ 14890 : 경사로 (Gold 3)

  • 구현만 열심히 해주면 된다.
#include <bits/stdc++.h>

using namespace std;

using vi = vector<int>;
using pi = pair<int,int>;
using ll = long long;

int mat[100][100];

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n, l; cin >> n >> l;
  for (int i=0;i<n;i++)
    for (int j=0;j<n;j++)
      cin >> mat[i][j];
  
  int ans = 0;
  for (int i=0;i<n;i++) {
    int prev = mat[i][0];
    bool okay = true;
    bool ramp[100] = {false, };
    for (int j=1;j<n;j++) {
      if (mat[i][j] > prev) {
        if (mat[i][j] > prev + 1) {
          okay = false; break;
        }
        for (int k=1;k<=l;k++) {
          if (j-k<0 || ramp[j-k] || mat[i][j-k] != prev) {
            okay = false; break;
          }
          ramp[j-k] = true;
        }
        if (!okay) break;
      } else if (mat[i][j] < prev) {
        if (mat[i][j] < prev - 1) {
          okay = false; break;
        }
        for (int k=0;k<l;k++) {
          if (j+k>n-1 || ramp[j+k] || mat[i][j+k] != prev-1) {
            okay = false; break;
          }
          ramp[j+k] = true;
        }
        if (!okay) break;
      }
      prev = mat[i][j];
    }
    if (okay) ans++;
  }

  for (int i=0;i<n;i++) {
    int prev = mat[0][i];
    bool okay = true;
    bool ramp[100] = {false, };
    for (int j=1;j<n;j++) {
      if (mat[j][i] > prev) {
        if (mat[j][i] > prev + 1) {
          okay = false; break;
        }
        for (int k=1;k<=l;k++) {
          if (j-k<0 || ramp[j-k] || mat[j-k][i] != prev) {
            okay = false; break;
          }
          ramp[j-k] = true;
        }
        if (!okay) break;
      } else if (mat[j][i] < prev) {
        if (mat[j][i] < prev - 1) {
          okay = false; break;
        }
        for (int k=0;k<l;k++) {
          if (j+k>n-1 || ramp[j+k] || mat[j+k][i] != prev-1) {
            okay = false; break;
          }
          ramp[j+k] = true;
        }
        if (!okay) break;
      }
      prev = mat[j][i];
    }
    if (okay) ans++;
  }

  cout << ans;

  return 0;
}

BOJ 13977 : 이항 계수와 쿼리 (Platinum 5)

  • 위의 ‘이항 계수 3’ 문제와 거의 동일하지만 팩토리얼만 전처리 해주면 된다.
  • 채점하는 데에 10분 넘게 걸린듯...
MOD = 1_000_000_007
fac = [0]*4_000_001
fac[0] = 1
for i in range(1, 4_000_001):
  fac[i] = fac[i-1] * i % MOD

for _ in range(int(input())):
  n, k = map(int, input().split())
  print(fac[n]*pow(fac[k] * fac[n-k],MOD-2,MOD)%MOD)
  • 이 문제로 인해 플래티넘 4를 달성하게 되었다. 한 달에 티어 하나씩 오르게 하고 싶다.