문제 링크 : https://www.acmicpc.net/problem/10067
문제 소개
길이 n의 수열을 k번 자른다. 자를 때 마다, 자른 후 양쪽 수열의 값의 각각의 합을 곱한 것만큼 점수를 얻는다. 이때 얻을 수 있는 최대 점수와 자르는 방법을 구하시오.
문제 풀이
우리는 이 문제를 최종 시점관점에서 보면서 점수에 어떤 값을 이 추가되었는지를 살펴보아야 한다.
(a,b,c,d)를 (a,b),(c,d)로 잘랐다고 하자. 이러면 우리는 (a+b)*(c+d)의 점수를 얻게 된다. 이를 다른 식으로 풀어쓰면 ac+ad+bc+bd 만큼의 점수를 얻게 된다는 것이다. 이는 서로 다른 그룹에 속하는 임의의 두 값을 곱해서 더한 값이다. 결국 그전에 어떤 순서로 어떻게 잘랐건 간에, 마지막에 다른 그룹에 있는 임의의 두 숫자를 곱한 값을 싹다 더하면 점수가 나오게 된다,
DP[i][j]=i번째 숫자까지, j개의 구간을 사용하여 얻을 수 있는 최댓값이라고 정의하자.
그러면 아래와 같은 식이 나오게 된다.
$DP[i][k]=\underset{j<i}{min}(sum(1\sim j)*sum({j+1}\sim i)+DP[j][k-1])$
이때 다른 문제에서도 그랬듯이 구간 합은 prefix sum으로 바꿈으로써 등식을 더 예쁘게 수정이 가능하다.
$DP[i][k]=\underset{j<i}{min}(pre[j]\times (pre[i]-pre[j])+DP[j][k-1])$
이 식은 CHT를 사용해서 풀 수 있고, 역추적까지 가능하다. 따라서 CHT를 이용하면 문제를 해결 할 수 있다.
코딩
- 코딩하는 과정에서 메모리가 넘치기 때문에 토글링을 해주었다.
- 들어오는 x값이 계속 증가하기 때문에 O(N) CHT를 사용하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
#define pii pair<ll,ll>
struct lin{
ll a,b;
};
struct CHT{
ll s=0,e=0;
ll idx[101010];
lin deq[101010];
double cross(int a, int b) {
return 1.0 * (deq[a].b - deq[b].b) / (deq[b].a - deq[a].a);
}
void insert(lin v,ll lidx){
deq[e] = v;
idx[e] = lidx;
while(s<e&&(deq[e].a==deq[e-1].a)&&deq[e-1].b<=deq[e-1].b){
deq[e-1] = deq[e];
idx[e-1] = idx[e];
e--;
}
while(s+1<e && cross(e - 2, e - 1) > cross(e - 1, e)){
deq[e-1] = deq[e];
idx[e-1] = idx[e];
e--;
}
e++;
}
pii query(ll x){
while(s+1<e && deq[s+1].b - deq[s].b >= x * (deq[s].a - deq[s+1].a))
s++;
return pii(deq[s].a*x+deq[s].b,idx[s]);
}
void re(){
s=0;
e=0;
}
}cht;
ll pre[101010];
ll dp[2][101010];
ll arr[101010];
int com[202][101010];
int ans[202];
int main(){
ll i,j,k,m,n;
scanf("%lld %lld",&n,&m);
for(i=1;i<=n;i++){
scanf("%lld",&arr[i]);
pre[i]=arr[i]+pre[i-1];
}
lin l;
for(i=1;i<=m;i++){
for(j=1;j<=n;j++)
dp[0][j]=dp[1][j];
for(j=i;j<=n;j++){
l.a = pre[j];
l.b = -pre[j]*pre[j]+dp[0][j];
}
}
printf("%lld\n",dp[1][n]);
for(i=m;i>=1;i--){
ans[i]=com[i][n];
n=com[i][n];
}
for(i=1;i<=m;i++)
printf("%d ",ans[i]);
}
Colored by Color Scripter
|
'코딩 > 백준 문제 풀이' 카테고리의 다른 글
BOJ 17408 - 수열과 쿼리 24 (0) | 2020.05.22 |
---|---|
BOJ 2463 - 비용 (0) | 2020.05.20 |
BOJ 7578 공장 (0) | 2020.04.14 |
BOJ 2357 - 최솟값과 최댓값 (0) | 2020.04.14 |
BOJ 1761 - 정점들의 거리 (0) | 2020.04.13 |