※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 10067번 문제인 수열 나누기이다.
문제는 아래 링크를 확인하자.

https://www.acmicpc.net/problem/10067 

 

10067번: 수열 나누기

첫째 줄에 두 정수 n과 k가 주어진다. (2 ≤ n ≤ 100,000, 1 ≤ k ≤ min(n-1, 200)) 둘째 줄에는 수열을 나타내는 음이 아닌 정수 n개 a1, a2, ..., an이 주어진다. (0 ≤ ai ≤ 104)

www.acmicpc.net

주어진 배열을 k+1조각냈을 때, 이 조각들이 어떤 순서로 조각났는지에 상관없이 얻는 점수는 일정함을 관찰하자.

 

주어진 배열(0-based index)의 [0,n] 구간을 k번 나눠 얻을 수 있는 점수의 최댓값을 dp[k][n]으로 정의하자. 이 때 위의 관찰을 이용하면 dp에 대하여 다음과 같은 점화식이 성립함을 알 수 있다. (단, Sp은 주어진 배열의 [0,p] 구간에 속하는 원소들의 합이다.)

dp[k][n]=max(dp[k1][m]+(Sm)(SnSm)(m<n)

 

위 식의 우변을 변형하면 다음과 같이 쓸 수 있다.

dp[k][n]=max((Sm)Sn+(dp[k1][m]Sm2))(m<n)

 

k값을 고정했을 때, 모든 dp[k1][n] 값들을 알고 있다면 위의 식에서 Sn만을 변수로 생각한다면 max 안에 들어있는 식은 Sm을 기울기로, dp[k1][m]Sm2로 갖는 일차함수로 생각할 수 있고 각 일차함수들의 기울기(Sm)와 대입할 변수(Sn)의 값은 단조증가하므로 O(n) CHT를 이용해 모든 dp[k][n]의 값을 계산할 수 있다. 

 

이 모든 dp[k][n] 값들을 모두 저장하기에는 메모리가 부족하고 위의 점화식에서 각 k에 대하여 k1에 대한 dp의 값만이 필요하므로, k1에 대한 dp값만을 계속 저장해 문제를 해결하자.

 

아래는 제출한 소스코드이다.

#include <iostream>
#include <vector>
#include <deque>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;

ll N; int K;
deque<pair<pll, int>> dq;
ll total = 0;

ll psum[100000];
int backstep[201][100000];
ll dp[100000];

ll func(int idx, ll x) {
	return dq[idx].first.first * x + dq[idx].first.second;
}

pair<ll, int> CHT1(ll x) {
	while (dq.size() > 1) {
		if (func(0, x) <= func(1, x)) dq.pop_front();
		else break;
	}

	ll p = dq[0].first.first, q = dq[0].first.second;
	return make_pair(p * x + q, dq[0].second);
}

void CHT2(ll a, ll b, int idx) {
	while (!dq.empty()) {
		int idx = dq.size() - 1;
		ll p = dq[idx].first.first, q = dq[idx].first.second;
		if (p < a) break;
		if (b >= q) dq.pop_back();
		else return;
	}
	while (dq.size() > 1) {
		int idx1 = dq.size() - 1, idx2 = dq.size() - 2;
		ll p1 = dq[idx1].first.first, q1 = dq[idx1].first.second;
		ll p2 = dq[idx2].first.first, q2 = dq[idx2].first.second;

		if ((q2 - b) * (a - p1) < (q1 - b) * (a - p2)) break;
		dq.pop_back();
	}
	dq.push_back(make_pair(make_pair(a, b), idx));
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	cin >> N >> K;
	for (int i = 0; i < N; i++) cin >> psum[i];
	for (int i = 1; i < N; i++) psum[i] += psum[i - 1];

	for (int k = 1; k <= K; k++) {
		dq.clear();
		dq.push_back(make_pair(make_pair(0, 0), -1));
		for (int i = 0; i < N; i++) {
			pair<ll, int> ret = CHT1(psum[i]);
			CHT2(psum[i], dp[i] - psum[i] * psum[i], i);
			dp[i] = ret.first, backstep[k][i] = ret.second;
		}
	}

	N--;

	cout << dp[N] << '\n';

	vector<int> ans;

	for (int k = K; k > 0; k--) {
		ans.emplace_back(backstep[k][N] + 1);
		N = backstep[k][N];
	}

	while (!ans.empty()) {
		cout << ans.back() << ' ';
		ans.pop_back();
	}
}
728x90

'BOJ' 카테고리의 다른 글

[BOJ 2878 // C++] 캔디캔디  (0) 2023.06.17
[BOJ 5254 // C++] Balls  (0) 2023.06.16
[BOJ 13230 // C++] Bits equalizer  (0) 2023.06.14
[BOJ 11334 // C++] Coin Turning Game  (0) 2023.06.13
[BOJ 13229 // C++] Selection Sum  (0) 2023.06.12

+ Recent posts