【LG5017】[NOIP2018pj]擺渡車
阿新 • • 發佈:2018-11-22
【LG5017】[NOIP2018pj]擺渡車
題面
題解
震驚!普及組竟然考斜率優化???
當然有其他的方法
首先我們轉化一下模型
此題可以變為:
在一根時間軸上有一些點,每個時間點\(i\)有一個權值\(c_i\)(即在\(i\)開始等待人數,沒有則為\(0\))
要求選一些時間點,每個時間點間隔不小於\(m\)
使得每個點的權值乘上它與第一個大於等於它時間的已選擇的時間點到它的距離之和最小 感覺講得好複雜
設\(dp[i]\)表示當我們強制選時間點\(i\)的最小值
則有轉移方程\(dp[i]=\min{dp[j]+\sum_{k=j+1}^i{(i-k)*c_k}}\) \((\)
次數直接轉移的複雜度為\(O(n^3)\)
考慮怎麼優化,設
\(sum1[i]=\sum_{j=0}^i{c_j}\)
\(sum2[i]=\sum_{j=0}^i{c_j*j}\)
然後方程化為\(dp[i]=\min dp[j]+i*sum1[i]-i*sum1[j]-sum2[i]+sum2[j]\) \((\)\(0\)\(\leq\)\(j\)\(\leq\)\(i-m\)\()\)
此時複雜度為\(O(n^2)\)
繼續優化,此時用上斜率優化
去掉\(min\),則
\(dp[i]=dp[j]+i*sum1[i]-i*sum1[j]-sum2[i]+sum2[j]\)
移項得\(dp[j]+sum2[j]=i*sum1[j]+dp[i]-i*sum1[j]+sum2[i]\)
將\(dp[j]+sum2[j]\)視為\(y\)
將\(i\)視為\(k\)
將\(sum1[j]\)視為\(x\)
佇列優化下凸殼即可
複雜度\(O(n)\)
但是好像會出現很多玄學問題啊~
下面這份使用\(double\)算斜率的程式碼交換\(slope\)函式中的\(i\)和\(j\)會\(WA\)
就是換成這樣:
inline double slope(int j, int i) { return 1.0 * (y(j) - y(i)) / (x(i) == x(j) ? 1e-9 : x(j) - x(i)); }
但是好像理論上沒問題啊?
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <climits>
using namespace std;
#define MAX_T 4100000
int N, M, c[MAX_T];
int maxt, sum1[MAX_T], sum2[MAX_T];
int dp[MAX_T], q[MAX_T];
#define y(i) (dp[i] + sum2[i])
#define k(i) (i)
#define x(i) (sum1[i])
inline double slope(int i, int j) { return 1.0 * (y(j) - y(i)) / (x(i) == x(j) ? 1e-9 : x(j) - x(i)); }
int main () {
cin >> N >> M;
for (int t, i = 1; i <= N; i++) cin >> t, c[t]++, maxt = max(maxt, t);
sum1[0] = c[0], sum2[0] = 0;
for (int i = 1; i < maxt + M; i++) {
sum1[i] = sum1[i - 1] + c[i];
sum2[i] = sum2[i - 1] + i * c[i];
}
int ans = INT_MAX;
int l = 1, r = 0;
for (int i = 0; i < maxt + M; i++) {
if (i - M >= 0) {
while (l < r && slope(q[r - 1], q[r]) >= slope(q[r], i - M)) --r;
q[++r] = i - M;
}
while (l < r && slope(q[l], q[l + 1]) <= k(i)) ++l;
dp[i] = i * sum1[i] - sum2[i];
int j = q[l]; if (l <= r) dp[i] = min(dp[i], dp[j] + i * sum1[i] - i * sum1[j] - sum2[i] + sum2[j]);
}
for (int i = maxt; i < maxt + M; i++) ans = min(ans, dp[i]);
cout << ans << endl;
return 0;
}
不用\(double\)不存在上面的問題:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <climits>
using namespace std;
#define MAX_T 4100000
int N, M, c[MAX_T];
int maxt, sum1[MAX_T], sum2[MAX_T];
int dp[MAX_T], q[MAX_T];
#define y(i) (dp[i] + sum2[i])
#define k(i) (i)
#define x(i) (sum1[i])
int main() {
cin >> N >> M;
for (int t, i = 1; i <= N; i++) cin >> t, c[t]++, maxt = max(maxt, t);
sum1[0] = c[0], sum2[0] = 0;
for (int i = 1; i < maxt + M; i++) {
sum1[i] = sum1[i - 1] + c[i];
sum2[i] = sum2[i - 1] + i * c[i];
}
int ans = INT_MAX;
int l = 1, r = 0;
for (int i = 0; i < maxt + M; i++) {
if (i - M >= 0) {
while (l < r && 1ll * (y(q[r]) - y(q[r - 1])) * (x(i - M) - x(q[r])) >=
1ll * (y(i - M) - y(q[r])) * (x(q[r]) - x(q[r - 1]))) -- r;
q[++r] = i - M;
}
while (l < r && 1ll * (y(q[l + 1]) - y(q[l])) <=
1ll * k(i) * (x(q[l + 1]) - x(q[l]))) ++l;
dp[i] = i * sum1[i] - sum2[i];
int j = q[l]; if (l <= r) dp[i] = min(dp[i], dp[j] + i * sum1[i] - i * sum1[j] - sum2[i] + sum2[j]);
}
for (int i = maxt; i < maxt + M; i++) ans = min(ans, dp[i]);
cout << ans << endl;
return 0;
}