Codechef July Challenge 2019 Hit the Coconuts
假设现在有一堆数,我想要保证能取出一个,至少需要敲 (数的个数)*(这些数里的最小值)
那么把这些数从大到小排序,$dp[i][j]$ 表示前 $i$ 个里面保证能取出 $j$ 个需要敲的次数。
$dp[i][k] = min(dp[j][k - 1] + (i - j) \times a[i])$
斜率优化解决。这里的 $a[i]$ 是单调递减的。即斜率是单调递减的。
那么下凸壳维护的最优决策点是越来越靠左的,所以pop的是右端,即是一个单调栈。
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 1e3 + 7; const double eps = 1e-10; int n, z; ll a[N], dp[N][N]; int que[N], l, r; inline ll X(int i) { return i; } inline ll Y(int i, int cur) { return dp[i][cur]; } inline double K(int i, int j, int cur) { return (Y(i, cur) - Y(j, cur)) / (X(i) - X(j)); } inline int dcmp(double x) { if (fabs(x) < eps) return 0; return x < 0 ? -1 : 1; } int main() { freopen("in.txt", "r", stdin); int T; scanf("%d", &T); while (T--) { scanf("%d%d", &n, &z); for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); sort(a + 1, a + 1 + n, greater<ll>()); for (int i = 1; i <= n; i++) dp[i][1] = i * a[i]; for (int k = 2; k <= z; k++) { que[l = 1] = 0; r = 0; que[++r] = k - 1; for (int i = k; i <= n; i++) { while (l < r && dcmp(K(que[r], que[r - 1], k - 1) - a[i]) >= 0) r--; int j = que[r]; dp[i][k] = dp[j][k - 1] + (i - j) * a[i]; while (l < r && dcmp(K(que[r], que[r - 1], k - 1) - K(que[r], i, k - 1)) >= 0) r--; que[++r] = i; } } ll ans = 1e18; for (int i = z; i <= n; i++) ans = min(ans, dp[i][z]); printf("%lld\n", ans); } return 0; }