본문 바로가기

Problem Solving/문제풀이

백준 13262번 수열의 OR 점수

반응형

문제 요약

크기가 N인 수열을 연속된 구간 K개로 나누려고 한다. 하나의 구간의 점수는 해당 구간에 속한 정수들의 OR값이다.

구간들의 점수의 합을 최대화하시오.

N, K는 최대 5000이다.

풀이

먼저 $C(i,j)$를 $a_i, a_{i+1}, \cdots , a_j$까지의 수들을 OR한 값이라고 하자.

그러면 다음과 같은 dp식이 나온다
$$
\begin{gather}
dp(i,j) = \text{수열을 1부터 j까지 i개의 구간으로 나눴을 때의 최댓값} \\
dp(i,j) = \underset{k<j}{max}(dp(i-1, k) + C(k+1, j))
\end{gather}
$$
C(i, j)랑 C(i,j+1)을 비교해보면 후자가 무조건 크거나 같다는 것을 알 수 있다.

$dp(i, j)$의 점화식에서 $dp(i,j)$의 값을 최대로 만들어주는 $k$의 값을 $opt_j$라고 하자.

$j < j'$일 때, 위 C의 특성때문에 $opt_j \le opt_{j'}$라는 것을 알 수 있다. 따라서, 분할정복 최적화를 적용할 수 있으며 $O(NKlogN)$에 문제가 해결 가능하다.

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

ll C[5005][5005];
ll arr[5005];
ll dp[5005][5005];
int N, K;

void solve(int lev, int s, int e, int optl, int optr) {
    if(s > e) return ;
    int mid = (s+e) >> 1;
    ll &ans = dp[lev][mid];
    ans = dp[lev-1][optl] + C[optl+1][mid];
    int opt = optl;
    for(int i=optl;i<min(mid, optr);++i) {
        ll val = dp[lev-1][i] + C[i+1][mid];
        if(opt == -1 || ans < val) {
            opt = i; ans = val;
        }
    }
    solve(lev, s, mid-1, optl, opt+1);
    solve(lev, mid+1, e, opt, optr);
}

int main() {
    cin.tie(nullptr); ios::sync_with_stdio(false);
    cin >> N >> K;
    for(int i=1;i<=N;++i) cin >> arr[i];
    for(int i=1;i<=N;++i) for(int j=i;j<=N;++j) C[i][j] = C[i][j-1] | arr[j];
    for(int i=1;i<=N;++i) dp[1][i] = C[1][i];
    for(int i=2;i<=K;++i) solve(i, i, N, i-1, N);
    cout << dp[K][N] << '\n';
    return 0;
}
반응형

'Problem Solving > 문제풀이' 카테고리의 다른 글

백준 12766번 지사 배정  (0) 2021.02.16
백준 11001번 김치  (0) 2021.02.16
백준 14636번 Money for Nothing  (0) 2021.02.16
백준 13261번 탈옥  (0) 2021.02.16
백준 15958번 프로도의 100일 준비  (0) 2021.02.15