Educational Codeforces Round 147 (Rated for Div. 2)


A. Matching (800)

A. Matching

처음이 $0$으로 시작하면 답은 $0$이다.

처음이 $?$로 시작하면 그건 $9$를 곱해주고,

나머지 $?$들은 모두 $10$을 곱해준다.

B. Sort the Subarray (1100)

B. Sort the Subarray

투포인터로 좌우의 끝부터 $a_i=b_i$ 이면 계속 좁혀주고 서로 다른 첫 구간을 $l', r'$ 라 하자.

이제 그 구간을 정렬할 것이므로 $a[l'-1]$ 가 구간에서 가장 작다면 왼쪽으로 확장, $a[r'+1]$ 가 구간에서 가장 크다면 오른쪽으로 확장할 수 있다.

void solve() {
   int n;
   cin >> n;
   vi a(n), b(n);
   fv(a);
   fv(b);
   int l = 0, r = n - 1;
   set<int> s;
   while (l < r) {
      if (a[l] == b[l]) {
         l++;
         continue;
      }
      if (a[r] == b[r]) {
         r--;
         continue;
      }
      break;
   }
   for (int i = l; i <= r; i++) s.insert(a[i]);
   while (l - 1 >= 0 && a[l - 1] <= *s.begin()) {
      s.insert(a[l - 1]);
      l--;
   }
   while (r + 1 < n && a[r + 1] >= *s.rbegin()) {
      s.insert(a[r + 1]);
      r++;
   }
   cout << l + 1 << ' ' << r + 1 << endl;
}
 

C. Tear It Apart (1300)

C. Tear It Apart

길이 $k$의 문자들을 모두 지우는 개수는 $f(k)=1+f\left( \left\lfloor \dfrac{k}{2} \right\rfloor \right)$ 로 DP로 전처리해둘 수 있다.

$26$개의 문자들을 모두 순회하며, 그 문자와 다른 것들을 하나의 블록으로 본다.

그 블록의 길이 들 중 가장 $f$가 큰 것이 그 문자만 남기는데 필요한 최소 횟수이다.

int remove_cost[200005];
void pre_calc() {
   remove_cost[1] = 1;
   for (int i = 2; i <= 200000; i++) {
      remove_cost[i] = 1 + remove_cost[i / 2];
   }
}
 
void solve() {
   string s;
   cin >> s;
   vvi idx(26);
   for (int i = 0; i < sz(s); i++) {
      idx[s[i] - 'a'].pb(i);
   }
   int ans = 1e9;
   for (int i = 0; i < 26; i++) {
      if (!sz(idx[i])) continue;
      int tmp = 0;
 
      // 시작
      maxa(tmp, remove_cost[idx[i][0]]);
      maxa(tmp, remove_cost[sz(s) - 1 - idx[i].back()]);
      for (int l = 0; l < sz(idx[i]) - 1; l++) {
         int len = idx[i][l + 1] - idx[i][l] - 1;
         maxa(tmp, remove_cost[len]);
      }
      mina(ans, tmp);
   }
   cout << ans << endl;
}

D. Black Cells (1900)

D. Black Cells

Observation 1

구간은 모두 떨어져있다.

Observation 2

길이 $2$이상의 구간은 스킵할 필요가 없다. 구간이 최소 $1$ 떨어져있기 때문에 shift를 눌렀다 떼는 동작을 아끼는거랑 다음 구간으로 넘어가는 최소 $2$번의 이동횟수와 같기때문이다.

Observation 3

길이 $1$의 구간은 이미 지나왔다면 모두 동일하게 생각해줄 수 있다.

이미 지나온 구간에서 어떤 길이 $1$의 구간을 먼저지났냐 늦게지났냐는 정답에 영향을 미치지 않는다.

Solution

나는 이분탐색을 썼지만 $O(n)$에 구할 수 있다.

현재 지나온 구간에서 길이 $2$이상인 구간의 총 길이 합을 $s$라고 하고, 길이 $1$인 구간의 총 길이 합을 $c$라 한다.

1- $s+c < k$ 라면 아직 모두 사용해도 채우지 못한다는 것이므로 넘긴다.

2- $s < k,~~ s+c \ge k$

긴 구간만으로 $k$를 채울 수 없지만 $1$구간들 몇개를 써서 채울 수 있다는 것을 의미한다.

이때의 정답은 $2 \cdot$ (써야하는 길이 $1$구간 수 + 지금까지 지나온 길이 $2$이상 구간수) + 현재 구간의 가장 끝의 위치 이다.

왜 현재 구간의 가장 끝 위치인지 $\Rightarrow$ 길이 $1$의 구간을 최소로 골라 $k$가 될 때 까지만 사용할 것이기 때문에 끝나는 구간은 현재 구간의 끝이다.

3- $s \ge k$

이땐 긴 구간들만 으로 $k$개를 채울 수 있으므로

$2 \cdot (\text{지금까지 지나온 2이상의 구간의 개수})+(L_i+\text{현재 구간에서 어디까지 가야하는지})$

가 정답이 된다.

2,3번 케이스에서 가장 작은 값을 정답으로 채택하면 된다.

E. Rearrange Brackets (2100)

E. Rearrange Brackets

올바른 괄호 문자열이 주어진다.

우선 문제에서 얘기하는 Minimum cost를 만드는 그리디한 방법은 뒤에서부터 () 인 괄호를 제거해나가는 것이다.

이를 다시 해석하면 ((())) 같은 친구는 2+1 이 소모되므로 어떤 ) 를 기준으로 자신을 감싸고 있는 괄호 쌍의 개수를 모두 더한것이 최소 비용이다.

따라서 우리의 목적은 최대한 겹쳐진 괄호를 제거하는 것에 있다는 걸로 재정의된다.

$k \le 5$이기 때문에 매 단계 직접 문자열을 구성해줄 수 있다.

어떤 괄호가 있고 인덱스가 $l, r$ 이라면 이 괄호를 없앰으로 얻을 수 있는 이득은 $\dfrac{r-l-1}{2}$이다.

따라서 항상 가장 바깥쪽에 있는 괄호를 재배열하는게 최적이다. 그리고 가장 바깥쪽의 괄호는 올바른 괄호 문자열에 항상 존재한다.

((())(())) 처럼 되어있다면 가장 바깥의 괄호를 앞이나 뒤로 붙여 ()(())(()) 처럼 만들어줄 수 있다.

따라서 괄호 중 $r-l$ 이 가장 큰 것중 하나를 다른 하나 옆으로 붙여주는것이 항상 최적이다.

이걸 $k$번 반복하면 된다.

void solve() {  
   int k;  
   cin >> k;  
   string s;  
   cin >> s;  
   int n = sz(s);  
   auto get_sub = [&](int l, int r) -> string {  
      int len = r - l + 1;  
      if (len <= 0) return "";  
      return s.substr(l, len);  
   };  
   while (k--) {  
      stack<int> t;  
      vector<array<int, 3>> change;  
  
      vi a(n);  
      for (int i = 0, o = 0; i < n; i++) {  
         if (s[i] == '(')o++;  
         else o--;  
         a[i] = o;  
      }  
  
      for (int i = 0; i < n; i++) {  
         if (s[i] == '(') t.push(i);  
         else {  
            int l = t.top(), r = i;  
            change.pb({l, r, a[r]});  
            t.pop();  
         }  
      }  
      if (!sz(change)) break;  
      sort(all(change), [&](auto &a, auto &b) {  
         return a[1] - a[0] > b[1] - b[0];  
      });  
      int l = change[0][0], r = change[0][1];  
      string news = get_sub(0, l - 1) + "()" + get_sub(l + 1, r - 1) + get_sub(r + 1, n - 1);  
      s = news;  
   }  
   ll ans = 0;  
   for (int i = 0, o = 0; i < n; i++) {  
      if (s[i] == '(')o++;  
      else o--;  
      if (s[i] == ')') {  
         ans += o;  
      }  
   }  
   cout << ans << endl;  
}

F. Timber (2600)

F. Timber

Comments