题意
给你n个数,将这些数分成若干组,每组数最少t个。分一个组的代价为这一组中所有数与该组中最小值的差值总和。求最小代价。
思路
思路来自:https://www.cnblogs.com/hua-dong/p/7818231.html
简化问题。首先每一组的元素大小一定是相近的,可以对数组进行排序,排序后的数组分成的组一定都是连续的段。
接下来就可以进行dp。数组s表示排序后数组的前缀和,数组a表示排序后的数组,数组f表示前i分组的最小代价。可以得到转移方程(枚举上一次分组的位置)
f
[
i
]
=
m
i
n
{
f
[
j
]
+
s
[
i
]
−
s
[
j
]
−
a
[
j
+
1
]
∗
(
i
−
j
)
}
(
j
≤
i
−
t
)
f[i] = min\{f[j]+s[i]-s[j]-a[j+1]*(i-j)\}(j\leq i-t)
f[i]=min{f[j]+s[i]−s[j]−a[j+1]∗(i−j)}(j≤i−t)
将式子展开,得到
f
[
i
]
−
s
[
i
]
=
f
[
j
]
−
s
[
j
]
+
a
[
j
+
1
]
∗
j
−
s
[
j
+
1
]
∗
i
f[i]-s[i]=f[j]-s[j]+a[j+1]*j-s[j+1]*i
f[i]−s[i]=f[j]−s[j]+a[j+1]∗j−s[j+1]∗i
进行代换:
y
=
f
[
j
]
−
s
[
j
]
+
s
[
j
+
1
]
∗
j
x
=
s
[
j
+
1
]
k
=
i
b
=
f
[
i
]
−
s
[
i
]
\begin{aligned} &y=f[j]-s[j]+s[j+1]*j\\ &x=s[j+1]\\ &k=i\\ &b=f[i]-s[i]\\ \end{aligned}
y=f[j]−s[j]+s[j+1]∗jx=s[j+1]k=ib=f[i]−s[i]
得到:
b
=
y
−
k
x
b=y-kx
b=y−kx
即对于直线
y
=
k
x
+
b
y=kx+b
y=kx+b找其最小截距。由于
k
=
i
k=i
k=i,斜率是递增的,所以需要维护一个下凸壳。维护可以用单调队列。
代码
//
// Created by yjq on 2019/9/4.
//
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define ull unsigned long long
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
const int maxn = 4e5 + 10;
int n, m;
ll a[maxn], f[maxn], s[maxn];
int q[maxn];
ll X(int i, int j) {
return a[j + 1] - a[i + 1];
}
ll Y(int i, int j) {
return f[j] - s[j] + j * a[j + 1] - (f[i] - s[i] + i * a[i + 1]);
}
ll DP(int i, int j) {
return s[i] + f[j] - s[j] + j * a[j + 1] - i * a[j + 1];
}
int main() {
__;
while (cin >> n >> m) {
memset(a, 0, sizeof a);
memset(s, 0, sizeof s);
memset(f, 0, sizeof f);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; ++i) {
s[i] = s[i - 1] + a[i];
}
int l = 0, r = 0;
q[++r] = 0;
for (int i = 1; i <= n; ++i) {
int cur = i - m + 1;
while (l + 1 < r && Y(q[l + 1], q[l + 2]) <= i * X(q[l + 1], q[l + 2]))
++l;
f[i] = DP(i, q[l + 1]);
if (cur < m)continue;
while (l + 1 < r && Y(q[r - 1], q[r]) * X(q[r], cur) >= X(q[r - 1], q[r]) * Y(q[r], cur))
--r;
q[++r] = cur;
}
cout << f[n] << endl;
}
return 0;
}