題目連結:https://www.luogu.com.cn/problem/P4544
解題思路:
首先我們可以將 \(N\) 家商店按照座標 \(X_i\) 從小到大排序(可能會有一些商店座標相同的情況,但是不影響)。
並且我們可以將家看做第 \(N+1\) 個商店(當然,\(F_{N+1} = 0\))。
定義狀態 \(F_{i,j}\) 表示在前 \(i\) 家商店買了恰好 \(j\) 噸飼料的總花費。
則狀態轉移方程為:
\[f_{i,j} = \min\limits_{0 \le k \le \min(j, F_i)} f_{i-1, j-k} + (X_i - X_{i-1}) \times (j-k)^2 + C_i \times k
\]
示例程式:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 505, maxk = 1e4 + 5;
long long f[maxn][maxk];
int K, E, n;
struct Node {
int x, f, c;
} a[maxn];
int main() {
scanf("%d%d%d", &K, &E, &n);
for (int i = 1; i <= n; i++)
scanf("%d%d%d", &a[i].x, &a[i].f, &a[i].c);
sort(a+1, a+n+1, [](Node a, Node b){
return a.x < b.x;
});
a[n+1] = {E, 0, 0};
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for (int i = 1, F = 0; i <= n+1; i++) {
F = min(K, F + a[i].f);
for (int j = 0; j <= F; j++) {
for (int k = 0; k <= min(j, a[i].f); k++) {
f[i][j] = min(f[i][j], f[i-1][j-k] + (long long)(a[i].x-a[i-1].x)*(j-k)*(j-k) + (long long)a[i].c * k);
}
}
}
printf("%lld\n", f[n+1][K]);
return 0;
}
但是演算法的時間複雜度為 \(O(N \times K^2)\),會超時。(資料水了點, TLE 了一組,拿了 95 分)
接下來考慮如何最佳化。
考試將 \(j-k\) 替換為 \(k\),上式變為:
\[f_{i,j} = \min\limits_{max(0, j-F_i) \le k \le j} f_{i-1,k} + (X_i - X_{i-1}) \cdot k^2 + C_i \cdot (j - k)
\]
其中,\(C_i \cdot j\) 是一個固定項(和 \(k\) 無關,可以提出來)
然後定義
\[g(i, k) = f_{i-1,k} + (X_i - X_{i-1}) \cdot k^2 - C_i \cdot k
\]
則
\(f_{i,j} = C_i \cdot j + \min\limits_{max(0, j-F_i) \le k \le j} g(i, k)\)
然後可以發現,隨著 \(j\) 的增大,\(k\) 的左邊界(\(max(0, j-F_i)\))是逐漸變大的,\(k\) 右邊界(\(j\))也是逐漸變大的,所以我們可以用 單調佇列 維護這個區間(任意時刻單調佇列的隊首對應的就是 \(\min\limits_{max(0, j-F_i) \le k \le j} g(i, k)\))。
使用單調佇列最佳化後,演算法的時間複雜度就變為了 \(O(NK)\)(狀態數 \(O(NK)\),狀態轉移 \(O(1)\))。
示例程式:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 505, maxk = 1e4 + 5;
long long f[maxn][maxk];
int K, E, n;
struct Node {
int x, f, c;
} a[maxn];
long long g(int i, int k) {
return f[i-1][k] + (long long) (a[i].x - a[i-1].x) * k * k - (long long) a[i].c * k;
}
int main() {
scanf("%d%d%d", &K, &E, &n);
for (int i = 1; i <= n; i++)
scanf("%d%d%d", &a[i].x, &a[i].f, &a[i].c);
sort(a+1, a+n+1, [](Node a, Node b){
return a.x < b.x;
});
a[n+1] = {E, 0, 0};
fill(f[0], f[0] + maxn*maxk, (long long)1e18);
f[0][0] = 0;
for (int i = 1, F = 0; i <= n+1; i++) {
F = min(K, F + a[i].f);
deque<int> que;
for (int j = 0; j <= F; j++) {
while (!que.empty() && que.front() < j - a[i].f)
que.pop_front();
while (!que.empty() && g(i, que.back()) >= g(i, j))
que.pop_back();
que.push_back(j);
f[i][j] = (long long) a[i].c * j + g(i, que.front());
}
}
printf("%lld\n", f[n+1][K]);
return 0;
}