CF868F Yet Another Minimization Problem
CF868F Yet Another Minimization Problem
题目大意比较清楚,这里就不扯了
最朴素的做法是直接
O
(
n
2
k
)
D
P
O(n^2k) \ \ \ DP
O(n2k) DP
发现会T上天
然后就有了决策单调性优化
意思就是设
d
p
[
i
]
[
j
]
表
示
前
i
个
数
,
分
成
j
段
的
最
小
值
是
多
少
dp[i][j] 表示前i个数,分成j段的最小值是多少
dp[i][j]表示前i个数,分成j段的最小值是多少
然后如果
d
p
[
i
]
[
j
]
是
由
d
p
[
k
]
[
j
−
1
]
dp[i][j] 是由 dp[k][j-1]
dp[i][j]是由dp[k][j−1]转移过来的,那么
d
p
[
i
−
1
]
[
j
]
一
定
是
从
d
p
[
l
]
[
j
−
1
]
转
移
过
来
的
(
l
<
=
k
)
dp[i-1][j]一定是从dp[l][j-1]转移过来的(l<=k)
dp[i−1][j]一定是从dp[l][j−1]转移过来的(l<=k)
具体证明要推推式子,这里就省略了
然后怎么做呢?
直接维护肯定是比较麻烦的(涉及到每个数出现的个数)
可以考虑先把dp数组分成k层
然后发现每次都是从上一层的转移过来的
然后再设
c
a
l
c
(
l
,
r
,
l
l
,
r
r
)
表
示
d
p
[
l
.
.
.
.
.
r
]
[
k
]
的
d
p
值
是
由
d
p
[
l
l
.
.
.
.
.
.
.
r
r
]
[
k
−
1
]
转
移
过
来
的
calc(l, r, ll, rr)表示dp[l.....r][k]的dp值是由dp[ll.......rr][k-1]转移过来的
calc(l,r,ll,rr)表示dp[l.....r][k]的dp值是由dp[ll.......rr][k−1]转移过来的
然后就发现这个东西是可以二分(分治)的
令
m
i
d
=
(
l
+
r
)
/
2
令mid = (l + r) / 2
令mid=(l+r)/2
然后找到
d
p
[
m
i
d
]
[
k
]
dp[mid][k]
dp[mid][k]的最优决策点p
然后再分治处理
c
a
l
c
(
l
,
m
i
d
−
1
,
l
l
,
p
)
和
c
a
l
c
(
m
i
d
+
1
,
r
,
p
,
r
r
)
calc(l, mid - 1, ll, p) 和 calc(mid + 1, r, p, rr)
calc(l,mid−1,ll,p)和calc(mid+1,r,p,rr)
然后就行了
关于费用的计算就直接像莫队一样移动一下L,R就行了(反正一共只会移动
n
l
o
g
n
nlogn
nlogn次)
代码可能好懂一些
代码中的dp数组的两维是反的
code:
// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define N 1000005
#define int long long
using namespace std;
int L = 1, R, f[23][N], a[N], cnt[N], n, m, ans, cur;
void get(int l, int r){//动态算一下贡献
while(L < l) cnt[a[L]] --, ans -= cnt[a[L]], L ++;
while(R > r) cnt[a[R]] --, ans -= cnt[a[R]], R --;
while(L > l) L --, ans += cnt[a[L]], cnt[a[L]] ++;
while(R < r) R ++, ans += cnt[a[R]], cnt[a[R]] ++;
}
void calc(int l, int r, int ll, int rr){//如上面说的
if(l > r) return;
int p = 0, mid = (l + r) >> 1;
for(int i = ll; i <= min(rr, mid - 1); i ++){//找dp[cur][mid]的最优决策点
get(i + 1, mid);
if(f[cur - 1][i] + ans < f[cur][mid])
f[cur][mid] = f[cur - 1][i] + ans, p = i;
}
calc(l, mid - 1, ll, p);//分治处理
calc(mid + 1, r, p, rr);
}
signed main(){
memset(f, 0x3f, sizeof f);
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
for(int i = 1; i <= n; i ++) get(1, i), f[1][i] = ans;//初始化
for(int i = 2; i <= m; i ++) cur = i, calc(1, n, 0, n - 1);//一层层计算
printf("%lld", f[m][n]);//输出
return 0;
}