Codeforces Round 876 (Div. 2)


A. The Good ArrayPermalink

A. The Good Array

그리디하게 1,k+1,2k+11, k+1, 2k+111을 채워넣는 것이 좋다.

ii 를 채워넣을 때, n+1in+1-i11로 채운다.

ii11부터 n2\left\lceil \dfrac{n}{2} \right\rceil 까지 순회한다.

이제 ii 를 다시 1n1 \to n 까지 보면서 어떤 ii에 대해 ik\left\lceil \dfrac{i}{k} \right\rceil 가 현재까지 합보다 많은 경우에는 정답에 +1+1 을 해준다.

image.png

그건 위 그림과 같은 경우이고 중간에 무조건 11하나를 끼워넣어서 정답을 만들 수 있기 때문이다.

O(1)O(1) 풀이Permalink

N2N \ge 2이고 a1=an=1a_1=a_n=1 으로 둬야한다.

\left\lceil \right\rceil 를 집어치우고 \left\lfloor \right\rfloor로만 따져보자.

1,k+1,2k+1,1, k+1,2k+1,\cdots 처럼 되므로 11 을 미리 포함시켜 줘버린다면

k,2k,k, 2k, \dots 처럼 배열에 11 이 찍히는 것과 다름없다.

a1=an=1a_1=a_n=1 으로 미리 빼버렸으므로 남은 배열의 길이는 n2n-2 이고 따라서 n2k+2\left\lfloor \dfrac{n-2}{k} \right\rfloor+2 가 정답이다.

left to right는 그렇다 치고 왜 right to left도 가능할까?

왼쪽에서 a1,ak+1a_1,a_{k+1} 사이의 거리는 kk 인데, 오른쪽에서 an,akn2k+1a_n,a_{k \cdot \left\lfloor \frac{n-2}{k} \right\rfloor +1} 사이의 거리는 항상 kk 이하이기 때문이다.

n(kn2k+1)n-\left( k\left\lfloor \dfrac{n-2}{k} \right\rfloor+1\right)n=qk+1n=qk+1 일 때 최대고, 식을 풀면

qk+1(kqk1k+1)=qk+1(qkk+1)=k \begin{aligned} qk+1-\left( k \left\lfloor \dfrac{qk-1}{k} \right\rfloor+1 \right) \\ =qk+1-\left( qk-k+1 \right)=k \end{aligned}

이여서 최대 kk 이다. \square

B. LampsPermalink

B. Lamps

aia_i 가 작은 순서대로 램프를 킨다고 생각하자.

broken 된 것은 xx의 개수에 포함되지 않으므로, 항상 aia_i 가 동일한 것의 개수가 AA 개일 때, 최대 Min(A,ai)\text{Min}(A,a_i) 개의 램프를 킬 수 있다.

그리디하게 bib_i가 큰 것부터 키고 그것이 aia_i 개가 됐다면 이제 aia_i 를 가진 램프들은 모두 부숴지고 x0x \coloneqq 0 으로 초기화된다.

만약 A<aiA < a_i 라서 모든 aia_i 를 가진 램프를 킬 수 있다면, 어떤 aj>aia_j > a_i 가 있어 aja_j 를 가진 램프들을 킨다고 생각하자.

이제 어떠한 시점에 A+ajA + a_j에서 킨 개수가 aia_i 가 되어 xajx \coloneqq a_j에서 킨 개수로 초기화가 된다.

즉, aia_i 가 다르다면 서로 영향을 미칠수 없다.

따라서 동일한 aia_i 램프들을 모아두고 bib_i 를 내림차순하여 Min(A,ai)\text{Min}(A, a_i) 개를 뽑아 bib_i들을 정답에 더해준다.

C. Insert Zero and Invert PrefixPermalink

C. Insert Zero and Invert Prefix

결론적으로 가장 뒤가 00이 아니면 항상 만들 수 있다.

111101 1 \cdots 1 1 0 는 항상 만들 수 있다.

1111x0\underbrace{ 11\cdots11 }_{ x\text{개} }0 가 있다면 xx 개의 00p=0p=0 을 골라 추가한다음에 p=xp=x 를 골라 xx개의 00들을 모두 11로 바꿔주면 된다.

그 이후 왼쪽에 다시 00이 나오는 것은 위에서 만든 구간과 독립적으로 동일하게 만들어줄 수 있다.

이를 관찰하는건 쉽고 이 생각을 잘 구현해야 하는 constructive 문제이다.

나는 앞에서부터 보면서 답을 구성하고 뒤집어 출력했다.

D. Ball SortingPermalink

D. Ball Sorting

문제를 보자마자 LIS가 떠올라 LIS에 포함되지 않은 것들을 최대한 그리디하게 kk개의 00을 붙여 어떻게 정답을 구하려고 했으나 실패했다.

문제를 못 푼 원인은 다음과 같다.

  1. LIS를 만드는 방법은 유일하지 않은데, 유일하지 않은 LIS의 형태로 고정된 답을 구하려 했다.
  2. kk가 커질수록 어느 시점부터 정답이 항상 NLISN-\vert LIS \vert가 된다는 성질을 알아낸 것에 생각이 매몰되어 다른 풀이법을 탐색하지 못했다.

풀이는 다음과 같다.

LIS를 고려해야 하는 문제인건 맞는데, 그것에서 아이디어만 차용하는 방식이다.

무작위 순서의 길이 NN의 배열 AA가 있을 때, 임의의 원소들을 원하는 위치로 보내 정렬시킬 수 있는 최소 연산 횟수는 NLIS(A)N-LIS(A)이다.

실제 어떤 원소들이 LIS에 포함되는지, 그렇지 않아서 옆에 00을 붙여 빠르게 올바른 위치로 보내줘야 하는지와 관계없이, DP를 정의해 일반적으로 문제를 해결하려 해보자.

c=0c=0 인 공을 붙인다는 것은 어떤 연속적인 구간을 원하는 위치로 이동시킬 수 있다는 말이다.

이 말은 이 연속적인 구간을 지워줄 수 있다는 말과 동치이다.

문제들에서 그리디한 결론에 의해 수열을 xx 개의 구간으로 나눠서 ~ 같은 테크닉은 꽤나 흔하니 염두해두자.

c=0c=0 인 공이 kk 개 있을 때, kk 개의 구간들을 선택하며 선택되지 않은 다른 구간들을 모두 모으면 증가수열인 경우의 증가수열의 최장 길이를 구하는 문제가 된다.

DP[i][j]=iDP[i][j]=i 번째 원소까지만 보며, 증가수열의 길이가 jj이고, ii 번째 원소를 증가 수열의 마지막 원소로 포함할 때, 증가수열에 포함되지 않는 구간의 최소개수

이렇게 정의하면 답을 바로 구할수있는건 아니지만 테이블을 모두 채우고 DP[i][j]=xDP[i][j]=x 라 함은 길이 jj인 증가수열이 xx 개의 공을 필을 필요로 한다는 것이기 때문에 k=xk=x 일 때의 정답을 NjN-j 로 업데이트 해줄 수 있다.

단, iNi \neq N 일 경우 ii 오른쪽 구간도 하나의 c=0c=0 인 공을 두는 것으로 쳐야하기 때문에 k=x+1k=x+1 일 때의 정답을 업데이트 해줌이 옳다.

아이보리님의 코드를 많이 참고했다.

void solve() {  
   int n;  
   cin >> n;  
   vi c(n + 1);  
   fv1(c);  
   vvi dp(n + 1, vi(n + 1, 1e9));  
  
   dp[1][1] = 0;  
   for (int i = 2; i <= n; i++) {  
      dp[i][1] = 1;  
      for (int j = 1; j <= i; j++) {  
         for (int k = 1; k < i; k++) {  
            if (c[k] < c[i]) {  
               if (k == i - 1)  
                  mina(dp[i][j], dp[k][j - 1]);  
               else  
                  mina(dp[i][j], dp[k][j - 1] + 1);  
            }  
         }  
      }  
   }  
   vi ans(n + 1, 1e9);  
   for (int i = 1; i <= n; i++) {  
      for (int j = 1; j <= i; j++) {  
         int need = dp[i][j];  
         if (i != n) need++;  
         if (need <= n)  
            mina(ans[need], n - j);  
      }  
   }  
   for (int i = 1; i <= n; i++) mina(ans[i], ans[i - 1]);  
   for (int i = 1; i <= n; i++) cout << ans[i] << ' ';  
   cout << endl;  
}

E. Decreasing GamePermalink

E. Decreasing Game

Comments