[editorial] ACM-ICPC 2008 Seoul Regional Problem B - MCS

  • wookayin
    wookayin

    안녕하세요 욱 입니다[...]
    편하게 쉬운문제 에디토리얼쓰기
    문제 설명
    ACGT로만 이루어진 길이가 k인 substring에 대하여 조성이 같으면(각 문자의 개수가 같으면) 같은 걸로 칠 때,
    가장 많이 등장하는 것의 개수는 몇 개인가요?
    풀이
    간단한 스트링 문제로 쉽다는 생각을 갖고 덤벼보면 어렵지 않게 금방 해결할 수 있었던 문제입니다.
    substring 에 길이 제한이 없다면 조금 어려울 수도 있겠지만 substring의 길이가 상수이므로, 가능한 substring 의 개수는 기껏해야 n-k+1 개입니다.
    두 스트링을 같다고 판별하는 조건은 각 문자의 개수이므로, 우리는 스트링에서 각 문자가 나타난 횟수만을 기억하고 있으면 충분합니다!
    그러니까 기본적으로 가능한 모든 substring들을 고려하여, 각 4가지 문자의 빈도수를 세고 이 빈도수에 따라 등장한 string의 수를 기억해 나가는 방식을 생각해 볼 수 있겠습니다.
    O(n)개의 string들에 대해서 빈도를 세는 것을 각각 O(k)에 하면 O(nk)가 되지만,
    substring 의 길이가 상수이기 때문에 앞에서부터 쭉 훑으면서 맨 뒤에 글자 하나를 붙이고 맨 앞의 글자 하나를 빼는 방식으로 하면 빈도를 계산하는 데에는 O(n)이면 충분합니다.
    조금 더 자세히 적어보면, 처음에 k개 문자를 읽으면서 A C G T 각 문자가 몇번 등장했는지를 기억하고 있다가, i번째 문자 A[i] 를 넣고 i-k번째 문자 A[i-k] 를 빼면 될 것입니다. 넣고 뺀다는 거는 각 문자의 빈도수를 하나 증가시키거나 하나 감소시킨다는 거겠죠.
    남은 것은 개수에 따라 스트링의 개수를 어떻게 기억하냐는 문제인데, k가 600 이하이므로 가능한 모든 경우는 600^4를 넘지 않습니다.
    따라서 A의 개수, C의 개수, G의 개수, T의 개수에 따라 최대 600^4 가지의 상태공간이 존재하는데 600^4 는 좀 큰 수이므로 배열로 counting 하기에는 부적절한데,
    잘 생각해보면 substring 의 개수는 기껏해야 60000개를 넘지 않으므로 map 등의 자료구조를 쓰면 되겠죠. 혹은 해쉬를 써도 되는데, 아무래도 해쉬보다는 map이 제일 편할 듯 싶습니다. map 연산 당 시간복잡도는 O(lgn) 이하이므로, 전체 시간복잡도는 O(n lgn)이 됩니다 :)
    map에서 key가 되는 부분은 int 값을 4개 가지는 구조체가 될 수도 있는데, 저 같은 경우는 600^4 가 64bit integer 로 표현되므로 가능한 모든 스트링에 (A의 개수)*600^3 + (C의 개수)*600^2 + (G의 개수)*600 + (T의 개수) 라는 값을 대응시켜서 풀었네요.
    사실 방법이 그렇게 어려운 문제가 아니기 때문에, 설명보다도 코드를 보시는 것도 좋을 듯 합니다 :D

    #include 
    #include 
    using namespace std;
    int k;
    char str[60006];
    long long hash(long long a, long long b, long long c, long long d)
    {
      return a * 216000000 + b * 360000 + c * 600 + d;
    }
    int main()
    {
      int T;
      scanf("%d", &T);
      while(T-- > 0)
      {
        scanf("%d %s", &k, str);
        int a, c, g, t, i;
        a = c = g = t = 0;
        map m;
        // 처음 K개의 문자는 그냥 개수를 셈
        for(i=0; i=k; ++i)
        {
          m[ hash(a,c,g,t) ] ++;
          if(str[i] == 0)    break;
          if(str[i] == 'A')  a++;
          if(str[i] == 'C')  c++;
          if(str[i] == 'G')  g++;
          if(str[i] == 'T')  t++;
          if(str[i-k] == 'A')  a--;
          if(str[i-k] == 'C')  c--;
          if(str[i-k] == 'G')  g--;
          if(str[i-k] == 'T')  t--;
        }
        int answer = 0;
        for(map::iterator it = m.begin(); it != m.end(); ++it)
        {
          if(answer < it->second)
            answer = it->second;
        }
        printf("%d\n", answer);
      }
      return 0;
    }
    
    [이 글은 과거 홈페이지에서 이전된 글입니다. 원문보기]

    15년 전
4개의 댓글이 있습니다.
  • VOCList
    VOCList

    헐 역시 욱신 이게 쉽나요 ㅠㅠ 짱이다
    이상 설리...


    15년 전 link
  • Kureyo
    Kureyo

    map을 안써도 모든 상태를 집어넣은 다음에 정렬시켜버려도 가능하지 않을까 싶습니다. :) 그..그냥 그렇다고요..헤헤


    15년 전 link
  • KimT
    KimT

    정렬해서 풀었던 1인입니다..
    계속 타임리밋걸려서 고생했었던..ㅠ
    아.. 대회 한참 지나고 뭔가 슬슬 올라오네요.
    매번 눈팅만 하다가 덕분에 회원가입했습니다.~ㅎ
    대회이후 아쉬움이 남는건.. 어쩔수가 없나봐요..


    15년 전 link
  • hyunhwan
    hyunhwan

    TLE를 어떤 방식으로 해결 하셨는지가 궁금하네요... 아마도 substring들을 저장하는 자료구조가 문제가 크지 않았을까 싶은데, 혹시 C++의 string과 algorithm의 조합으로 구현을 하셨는지? 아마 그렇게 된다면 시간복잡도가 O(nmlogn) - n : substring의 개수, m : 문자열의 길이; 만큼의 시간복잡도가 걸려서 TLE가 되버리지 않았을까 하는 생각이 듭니다.


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