[editorial] SRM 392 Div 1

  • Toivoa
    Toivoa

    안녕하세요 Toivoa 입니다.
    개인적으로는 Submit도 못해보고 0점맞은 뜻깊은 SRM이었습니다. ㅠㅠ (지못미 rating)
    다음번에는 한국인 중 1등으로 editorial 쓸 수 있기를 바라면서 써봅니다. :D
    srm392_div1.PNG

    Easy (250 pts.)

    • 문제 설명 wildcard ('*')가 하나 포함된 string이 두 개 주어질 때, 두 문자열을 모두 만족하는 가장 짧은 문자열을 찾는 것입니다. wildcard는 null로 치환할 수 있습니다. 예를 들어 TO*CODER와 TOPCOD*ER가 주어진다면 TOPCODER가 됩니다. [spoiler="더 보기..."]
    • 문제 해법 문제에서 wildcard가 하나라고 되어 있으니 다른 많은 방법이 있겠지만, wildcard의 개수에 구애받지 않는 DP로도 풀 수 있습니다. 여기에서는 DP로 설명하겠습니다. 먼저 첫 번째 string을 s1, 두 번째 string을 s2라 할 때, table[x][y] : s1에서 처음부터 x개, s2에서 처음부터 y개의 문자열을 이용해서 만들어지는 가장 짧은 string의 길이 로 정의합니다. 이렇게 정의한 table에서 table[0][0] = 0 이 되며, table[0][k], table[k][0] = inf 가 되는 것은 쉽게 알 수 있습니다. 이 table을 채워나갈 수 있는 방법은 다음 6가지 경우가 있습니다.
    • s1[x]와 s2[y]가 모두 wild card인 경우, table[x + 1][y + 1] = table[x][y] 가 됩니다. (여기에서 s1[x], s2[y]는 0-based입니다.) -> 양쪽 다 wildcard이니 글자를 더 추가하지 않아도 됩니다.
    • s1[x] == s2[y]인 경우, 혹은 s1[x]가 wildcard인 경우, 또는 s2[y]가 wildcard인 경우. table[x + 1][y + 1] = table[x][y] + 1 이 됩니다. -> 둘 다 같은 글자이니 대각선 위에서 뒤에 한 글자 추가해서 내려올 수 있습니다. -> 둘 중 하나가 wildcard이니 대각선 위에서 wildcard가 아닌 쪽의 한 글자를 추가해서 내려올 수 있습니다.
    • s1[x]가 wildcard인 경우 table[x + 1][y + 1] = table[x][y + 1] 이 됩니다. -> 이 경우는 s1[x]의 wildcard를 null로 치환하는 형태입니다.
    • s1[x]가 wildcard인 경우 table[x + 1][y + 1] = table[x + 1][y] + 1 이 됩니다. -> wildcard에 s2[y]를 추가하는 형태입니다.
    • s2[y]가 wildcard인 경우 table[x + 1][y + 1] = table[x][y + 1] + 1 이 됩니다.
    • s2[y]가 wildcard인 경우 table[x + 1][y + 1] = table[x + 1][y] 이 됩니다. -> 5, 6은 3, 4와 같은 형태입니다. 위 경우에서 table[x + 1][y + 1]이 가장 작은 수가 되는 경우를 취하면 되고, 리턴 문자열은 경로를 기억해 두었다가 역추적하면 됩니다. 그런데 이렇게만 하면 맨 앞에 wildcard가 있는 경우는 제대로 처리하지 못합니다. 이 때는 table[1][0] = 0 과 같은 형태로 초기값을 넣어주면 됩니다. 여기까지 이해하셨다면 이유는 쉽게 아실 수 있으리라 믿습니다. ^^ ~~~ cpp

    int table[100][100];
    int from[100][100];
    const int INTMAX = 1000000000;
    class TwoStringMasks
    {
    public:
    string shortestCommon(string s1, string s2)
    {
    int l1 = s1.length(), l2 = s2.length();
    int i, j;
    for (i = 0; i <= l1; i++) for (j = 0; j <= l2; j++) table[i][j] = INTMAX;
    table[0][0] = 0;
    if (s1[0] == '*') table[1][0] = 0;
    if (s2[0] == '*') table[0][1] = 0;
    for (i = 0; i < l1; i++)
    {
    for (j = 0; j < l2; j++)
    {
    if (s1[i] == '*' && s2[j] == '*')
    {
    table[i + 1][j + 1] = table[i][j];
    from[i + 1][j + 1] = 0;
    }
    if (s1[i] == s2[j] || s1[i] == '*' || s2[j] == '*')
    {
    if (table[i + 1][j + 1] > table[i][j])
    {
    table[i + 1][j + 1] = table[i][j] + 1;
    from[i + 1][j + 1] = 1;
    }
    }
    if (s1[i] == '*')
    {
    if (table[i + 1][j + 1] > table[i][j + 1])
    {
    table[i + 1][j + 1] = table[i][j + 1];
    from[i + 1][j + 1] = 2;
    }
    if (table[i + 1][j + 1] > table[i + 1][j] + 1)
    {
    table[i + 1][j + 1] = table[i + 1][j] + 1;
    from[i + 1][j + 1] = 3;
    }
    }
    if (s2[j] == '*')
    {
    if (table[i + 1][j + 1] > table[i][j + 1] + 1)
    {
    table[i + 1][j + 1] = table[i][j + 1] + 1;
    from[i + 1][j + 1] = 4;
    }
    if (table[i + 1][j + 1] > table[i + 1][j])
    {
    table[i + 1][j + 1] = table[i + 1][j];
    from[i + 1][j + 1] = 5;
    }
    }
    }
    }
    if (table[l1][l2] >= INTMAX) return "impossible";
    int x = l1, y = l2;
    char cc[1000];
    int pos = 0;
    while (x >= 0 && y >= 0)
    {
    char c = '*';
    if (from[x][y] == 0)
    {
    x--; y--;
    continue;
    }
    else if (from[x][y] == 1)
    {
    c = s1[x - 1];
    if (c == '*') c = s2[y - 1];
    x--; y--;
    }
    else if (from[x][y] == 2)
    {
    x--;
    }
    else if (from[x][y] == 3)
    {
    c = s2[y - 1];
    y--;
    }
    else if (from[x][y] == 4)
    {
    c = s1[x - 1];
    x--;

    }
    else if (from[x][y] == 5)
    {
    y--;
    }
    if (c != '*') cc[pos++] = c;
    }
    cc[pos] = 0;
    reverse(cc, cc + pos);
    return cc;
    }
    };
    [/Code]
    [/spoiler]

    Medium (500 pts.)

    • 문제 설명 1 부터 N까지의 번호를 가진 N개의 cell이 원형(시계방향)으로 배열되어 있으며, cell 위에는 token들이 있습니다. 당신은 이 cell들 중 하나를 선택해서 얻을 수 있는 token의 개수를 최대로 하려 합니다. 현재 어떤 cell이 선택되어 있을 때, 현재 cell의 번호를 이루는 숫자들의 합 만큼 시계방향으로 이동한 cell이 다음으로 선택됩니다. 예를 들어 cell의 개수가 4인 경우 1번 cell에서는 2번, 2번에서는 4번, 3번에서는 2번, 4번에서는 4번 cell이 다음으로 선택되는 cell이 됩니다. [spoiler="더 보기..."]
    • 문제 해법 선택되는 cell들을 따라가다 보면 cycle이 생기게 됩니다. 이 때 cycle 위에 있는 cell들을 맨 처음에 선택한다면 cycle의 길이만큼의 token을 얻게 됩니다. 만약 cycle을 이루지 않는 cell을 처음 선택한다면 다음 선택되는 cell에서 얻을 수 있는 token의 개수 + 1개를 얻게 됩니다. 따라서 cell들을 따라가다가 cycle을 찾으면 계산해서 돌아오는 식으로 쉽게 풀 수 있습니다. 아래 코드는 이를 비재귀로 구현한 코드입니다. [code cpp] int sz[200000]; int vec[200000]; int lv[200000]; int used[200000]; class RoundAboutCircle { public: int getMovnum(int t) { int ret = 0; while (t != 0) { ret += t % 10; t /= 10; } return ret; } void calc(int t, int N) { int p = 0, rz = 2147483647; vec[p] = t; used[t] = 1; lv[t] = p + 1; while (1) { t += getMovnum(t + 1); t %= N; vec[++p] = t; if (used[t]) { if (lv[t] != 0) { // cycle int ln = p - lv[t] + 1; for (int i = 0; i < ln; i++) { sz[vec[p--]] = ln; lv[vec[p]] = 0; } } break; } lv[t] = p + 1; used[t] = 1; } while (p > 0) { int nex = vec[p]; int now = vec[p - 1]; sz[now] = sz[nex] + 1; lv[now] = 0; --p; } } int maxScore(int N) { int ret = 0, i; fill(used, used + N, 0); for (i = 0; i < N; i++) { if (used[i] == 0) calc(i, N); ret >?= sz[i]; } return ret; } };
    [/spoiler]
    
    
    
    
    Hard (1000 pts.)
    
    * 문제 설명
    어떤 수 x가 equidigit라는 것은 x를 이루는 숫자들의 개수의 합이 모두 같은 것을 의미합니다. (물론 leading zero는 없습니다.)
    예를 들어 5, 239, 333888, 566353이 equidigit가 됩니다.
    어떤 수 n이 주어졌을 때 n 이상인 가장 작은 equidigit를 찾는 것이 문제입니다.
    [spoiler="더 보기..."]
    * 문제 해법
    1. 이 문제에서 답은 주어진 n의 자리수를 증가시키지 않습니다. (같은 자리수로 모두 9로만 채우면 equidigit가 됩니다.)
    2. 이 성질을 이용해서 사용할 수 있는 숫자의 가지수를 찾을 수 있습니다. (자리수 % 가지수 == 0인 경우)
    3. 자리수와 숫자의 가지수가 결정되면 답이 될 수 있는 후보는 백트래킹으로 간단히 찾을 수 있습니다.
    4. 후보들 중에서 가장 작은 수가 답이 됩니다.
    ~~~ cpp
    
    typedef long long ll;
    int ln;
    const ll MAXDIGIT = 9223372036854775807LL;
    int used[10];
    int seld[10];
    int uval[10];
    class EquiDigitNumbers
    {
    public:
    ll solve(ll now, int lv, const string& n, const int vals, const int uselimit)
    {
     int i, j;
     if (lv == ln) return now;
     for (i = n[lv] - 48; i < 10; i++)
     {
      // can use
      if (i != n[lv] - 48)
      { // any numbers
       for (j = 0; j < vals; j++)
       {
        if (uval[j] == i) break;
        if (uval[j] == -1)
        {
         seld[i] = 1;
         uval[j] = i;
         break;
        }
       }
       if (j == vals) continue;
       if (used[i] == uselimit) continue;
       used[i]++;
       lv++;
       for (j = 0; j < vals; j++)
       {
        if (uval[j] == -1)
        {
         for (int k = 0; k < 10; k++)
         {
          if (seld[k] == 0)
          {
           seld[k] = 1;
           uval[j] = k;
           break;
          }
         }
        }
       }
       now = now * 10 + i;
       while (lv != ln)
       {
        for (j = 0; j < 10; j++)
        {
         if (seld[j] && used[j] < uselimit)
         {
          now = now * 10 + j;
          used[j]++;
          lv++;
          break;
         }
        }
       }
       return now;
      }
      else
      {
       for (j = 0; j < vals; j++)
       {
        if (uval[j] == i) break;
        if (uval[j] == -1)
        {
         seld[i] = 1;
         uval[j] = i;
         break;
        }
       }
       if (j == vals) continue;
       if (used[i] == uselimit) continue;
       used[i]++;
       ll t = solve(now * 10LL + i, lv + 1, n, vals, uselimit);
       if (t != MAXDIGIT) return t;
       used[i]--;
       if (used[i] == 0) 
       {
        seld[i] = 0;
        for (j = 0; j < vals; j++)
        {
         if (uval[j] == i) uval[j] = -1;
        }
       }
      }
     }
     return MAXDIGIT;
    }
    ll findNext(string n)
    {
     int i;
     ll ret = MAXDIGIT;
     ln = n.length();
     for (i = 1; i <= 10; i++)
     {
      if (ln % i == 0)
      {
       fill(uval, uval + i, -1);
       fill(used, used + 10, 0);
       fill(seld, seld + 10, 0);
       ret <?= solve(0, 0, n, i, ln / i);
      }
     }
     return ret;
    }
    };

    [/spoiler]

    [이 글은 과거 홈페이지에서 이전된 글입니다. 원문보기]

    16년 전
2개의 댓글이 있습니다.
  • Being
    Being

    매치 때도 이야기했지만 이지는 문자열 A, B라고 할 때
    A' = A에서 와일드카드 제거, B'도 비슷하게
    했을 때,
    A의 와일드카드 자리에 B'의 어떤 부분문자열을 넣을 것인지,
    B의 와일드카드 자리에 A'의 어떤 부분문자열을 넣을 것인지,
    무식하게 모든 경우를 brute-force로 시도해 보아도 시간 안에 나옵니다 lol


    16년 전 link
  • Toivoa
    Toivoa

    내가 매치때 그렇게 풀다가 문제 잘못 이해하고, 코드 꼬여서 submit도 못했어 ㅠㅠ


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