[editorial] Online Round 2 - A, B.

  • User Avatar
    Toivoa

    A와 B번 문제에 대한 editorial입니다. C나 D는 다른 분이 써주실거라 기대합니다.
    [A - Elegant Diamond]
    어떤 다이아몬드가 우아하다(elegant)라고 하기 위해서는 다이아몬드의 특정 위치의 숫자(digit)가 다이아몬드의 중심선을 기준으로 수직으로 대칭인 부분의 숫자와 같아야 하며, 수평으로 대칭인 부분의 숫자와 같아야 합니다.
    다이아몬드가 주어졌을 때 돈을 들여서 다이아몬드의 크기를 키울 수 있습니다. 다이아몬드를 키우는데 드는 비용은 (키우고 난 이후 다이아몬드를 이루는 숫자의 개수 - 기존 다이아몬드를 이루는 숫자의 개수) 입니다.

    우아한 다이아몬드를 만들기 위한 최소의 비용을 구해야 합니다.

    저는 배열 내에 다이아몬드 모양으로 입력받은 후에, 어떤 위치가 중점일 때 다이아몬드가 우아한지 체크해보는 방법을 이용하였습니다. 선택 가능한 중점은 입력받은 중점과 같은 중점을 기준으로 크기가 k * 2인 다이아몬드의 내부 전체입니다. 이렇게 체크해보면 모든 경우에 대해 체크할 수 있습니다. (다이아몬드의 크기가 k * 2가 되면 중점을 기준으로 한 사분면으로 모두 모을 수 있기 때문에 더 큰 범위에 대한 체크는 불필요합니다.)
    아래는 소스코드입니다. 헤더 파일을 include 하는 부분은 생략했습니다.
    ~~~ cpp

    int sz[500];
    int table[500][500];
    int k;
    bool chk(int cx, int cy)
    {
    int x = 240;
    for (int i = 1; i <= k; ++i)
    {
    int y = 240 - i;
    for (int j = 1; j <= i; ++j)
    {
    int px = x + 2 * (cx - x);
    int py = y + 2 * (cy - y);
    if (table[px][y] != -1 && table[px][y] != table[x][y])
    return false;
    if (table[x][py] != -1 && table[x][py] != table[x][y])
    return false;
    y += 2;
    }
    ++x;
    }
    for (int i = k - 1; i > 0; --i)
    {
    int y = 240 - i;
    for (int j = 1; j <= i; ++j)
    {
    int px = x + 2 * (cx - x);
    int py = y + 2 * (cy - y);
    if (table[px][y] != -1 && table[px][y] != table[x][y])
    return false;
    if (table[x][py] != -1 && table[x][py] != table[x][y])
    return false;
    y += 2;
    }
    ++x;
    }
    return true;
    }
    int main()
    {
    int t, cases = 0;
    scanf("%d", &t);
    for (int i = 1; i < 500; ++i)
    {
    sz[i] = ((i * (i + 1)) + (i * (i - 1))) / 2;
    }
    while (t--)
    {
    fill(table[0], table[500], -1);
    scanf("%d", &k);
    int res = 10000000;
    int x = 240;
    for (int i = 1; i <= k; ++i)
    {
    int y = 240 - i;
    for (int j = 1; j <= i; ++j)
    {
    scanf("%d", &table[x][y]);
    y += 2;
    }
    ++x;
    }
    for (int i = k - 1; i > 0; --i)
    {
    int y = 240 - i;
    for (int j = 1; j <= i; ++j)
    {
    scanf("%d", &table[x][y]);
    y += 2;
    }
    ++x;
    }
    int cx = 240 + k - 1, cy = 239;
    x = 240 - k;
    for (int i = 1; i <= k * 2; ++i)
    {
    int y = 240 - i;
    int z = i * 2 - 1;
    for (int j = 1; j <= z; ++j)
    { // x, y is center
    int nk = abs(cx - x) + abs(cy - y) + k;
    if (chk(x, y))
    {
    res = min(res, sz[nk]);
    }
    ++y;

    }
    ++x;
    }
    for (int i = k * 2 - 1; i > 0; --i)
    {
    int y = 240 - i;
    int z = i * 2 - 1;
    for (int j = 1; j <= z; ++j)
    { // x, y is center
    int nk = abs(cx - x) + abs(cy - y) + k;
    if (chk(x, y))
    {
    res = min(res, sz[nk]);
    }
    ++y;

    }
    ++x;
    }
    printf("Case #%d: %d\n", ++cases, res - sz[k]);
    }
    }

    [B - World Cup 2010]
    월드컵에 진출한 팀들의 경기 토너먼트가 있습니다. 각 경기의 입장권의 가격은 모두 다릅니다. (small input에서는 가격이 항상 1입니다.) Varva는 각 팀들에 대해 팀들이 치르게 되는 경기들 중 최대 M[i] 경기를 제외하고 모두 보려고 합니다. 이를 만족하면서 경기의 입장권을 사려고 할 때 필요한 최소의 비용은 얼마인지 구해야 합니다.
    <img src="http://code.google.com/codejam/contest/images/?image=wc2010.png&amp;p=698485&amp;c=635102" width="0" height="0" link_url="http://code.google.com/codejam/contest/images/?image=wc2010.png&amp;p=698485&amp;c=635102" editor_component="image_link" />
    small input은 가격이 1이라는 것을 이용해서 결승전부터 아래로 보면서 greedy하게 선택하면 됩니다. 이것에 대해서는 자세한 설명과 소스 코드는 생략합니다.
    저는 large input을 풀기 위해서 다음과 같은 점화식을 세워서 풀었습니다.
    T[X, Y]: X번 경기까지 고려했을 떄 (X번 경기의 입장권을 사는지 여부는 중요하지 않습니다.) 이 경기와 관련된 팀들 중 반드시 보고 싶은 경기의 수가 최대 Y가 될 때의 최소 비용.
     -> X번 경기를 고려했다는 것은 X번 경기 이하 라운드 (예를 들어 X번 경기가 결승전인 경우 모든 경기, 준결승전인 경우 해당 준결승전을 root로 하는 subtree)를 모두 고려했다는 것입니다.
     -> 이렇게 할 때 결승전의 번호를 1번, 준결승을 2, 3번, 8강전을 4, 5, 6, 7번, ... 으로 하면 이진트리의 특성으로 바로 하위 경기의 번호를 쉽게 알 수 있습니다.
     -> 각 팀에서 반드시 보고 싶은 경기의 수는 P - M[i]가 됩니다.
    T를 위와 같이 정의하면서 가장 하위의 경기는 각 팀으로 합니다. 예를 들어서 문제의 그림에서 8번 경기는 아르헨티나, 9번 경기는 독일, ... 으로 힙니다.
    그러면 초기화는 각 팀의 경기에서 반드시 보고 싶은 경기 수 (P - M[i]) 에 0을, 나머지는 INF로 채워주면 됩니다.
    이후 T[X, Y]는 아래 값들의 최소값이 됩니다.
     - min(T[X * 2, 0 ... Y] + T[X * 2 + 1, Y], T[X * 2, Y] + T[X * 2 + 1, 0 ... Y])    // X번 경기를 보지 않는 경우입니다.
     - min(T[X * 2, 0 ... Y + 1] + T[X * 2 + 1, Y + 1], T[X * 2, Y + 1] + T[X * 2 + 1, 0 ... Y + 1]) + X번 경기의 가격     // X번 경기를 보는 경우입니다.
    이 테이블을 다 채운 후 답은 T[1, 0]이 됩니다.
    ~~~ cpp
    
    typedef long long ll;
    #define INF 35184372088832LL
    int s2[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192};
    int vals[8192];
    ll res[2048][12];
    int cost[1024];
    int main()
    {
     int t, cases = 0;
     scanf("%d", &t);
     while (t--)
     {
      int p;
      scanf("%d", &p);
      fill(teams[0], teams[1024], 0);
      fill(res[0], res[2048], INF);
      for (int i = 0; i < s2[p]; ++i)
      {
       scanf("%d", &vals[i]);
       vals[i] = p - vals[i];
       res[s2[p] + i][vals[i]] = 0;
      }
      for (int i = p - 1; i >= 0; --i)
      {
       for (int j = 0; j < s2[i]; ++j)
       {
        scanf("%d", &cost[s2[i] + j]);
       }
      }
      for (int i = s2[p] - 1; i >= 1; --i)
      {
       for (int j = 0; j <= p; ++j)
       {
        const ll& js = res[i * 2][j];
        if (js == INF) continue;
        for (int k = 0; k <= p; ++k)
        {
         const ll& ks = res[i * 2 + 1][k];
         if (ks == INF) continue;
         int tx = max(j, k);
         ll s = js + ks;
         res[i][tx] = min(res[i][tx], s);
         if (tx != 0)
          res[i][tx - 1] = min(res[i][tx - 1], s + cost[i]);
        }
       }
      }
      printf("Case #%d: %I64d\n", ++cases, res[1][0]);
     }
    
    
    [이 글은 과거 홈페이지에서 이전된 글입니다. 원문보기]

    16년 전
3개의 댓글이 있습니다.
  • User Avatar
    VOCList

    간지 ㅜㅜ


    16년 전 link
  • User Avatar
    Taeyoon_Lee

    응? 8강에 일본? 나이지리아?


    16년 전 link
  • User Avatar
    kcm1700

    B 문제는 굳이 DP로 안 하더라도 Divide & Conquer로도 해결할 수 있습니다. 다만 메모리를 이용하지 않는 만큼 시간은 증가해버렸습니다. O(n2) (여기서 n은 총 팀의 수)


    16년 전 link
  • 정회원 권한이 있어야 커멘트를 다실 수 있습니다. 정회원이 되시려면 온라인 저지에서 5문제 이상을 푸시고, 가입 후 7일 이상이 지나셔야 합니다. 현재 문제를 푸셨습니다.