UVA12170 Easy Climb 题解(单调队列优化DP)
请喜之郎在第六次化峥的路上遇到了 \(n\) 座大山排成一排,编号从 \(1\) 到 \(n\)。每座山有高度 \(h_i\)。
请喜之郎精通奇门遁甲之术,能用法术移动山石。一次操作中,喜之郎选定一座山 \(2 \le i \le n - 1\),令
\(h_i \leftarrow h_i - 1\) 或 \(h_i \leftarrow h_i + 1\),
同时操作不能使山的高度修改为负数。特别注意:操作不能改变第一座山和最后一座山的高度!
爬山艰辛劳累,为了保存喜之郎的体力,喜之郎希望操作结束后任意相邻两座山的高度差绝对值不大于 \(d\)。即要求满足:
\[|h_i - h_{i+1}| \le d \quad (1 \le i < n). \]求达到这一条件的最小操作次数。如果无解,输出 \(-1\)。
这个题考场上想到 slope trick 去了。但是事实上不需要那么麻烦(slope trick 是否可做其实我还不知道)。
一个朴素的想法是,设 \(f_{i,j}\) 为考虑了前 \(i\) 个数,第 \(i\) 个数改为了 \(j\) 的最小代价,有转移式:
要优化这个 DP,首先需要观察到一个非常厉害的性质:如果我们 \(a_{i-1}\) 确定了,那么 \(a_i\) 其实只有三种取值:\(a_{i-1}-d,a_i,a_{i-1}+d\),分别对应:在区间下方要移动到区间的最低点,在区间里不需要更改,在区间上方要移动到区间的最高点。
这个性质其实还是好观察到的。因为假设你没有移动到一个极值点,但是你移动了,那么你移动到对应的极值点一定不劣(你 \(a_i\) 移动了,后面的 \(a_{i+1}\) 就不需要移动了),且合法性也不劣(之前合法的现在还是合法)。
然后,根据这个性质,你就可以知道,所有合法的 \(j\) 一定能被表示为某个 \(a_t+kd\) 的形式,因为你每次修改一定会把他修改为上一个的 \(d\) 倍,或者干脆不修改。于是,所有合法的取值其实只有 \(O(n^2)\) 种,所以你第二维可以优化到 \(O(n^2)\) 的级别。
于是你用单调队列优化转移,最后时间复杂度是 \(O(n^3)\) 的。
const int MAXN = 305;
int n, d, a[MAXN], f[2][MAXN * MAXN];
void work() {
cin >> n >> d;
for (int i = 1; i <= n; ++i)
cin >> a[i];
if (abs(a[n] - a[1]) > (n - 1) * d)
return cout << -1 << endl, void();
set<int> st;
for (int i = 1; i <= n; ++i) {
for (int j = -n; j <= n; ++j) {
int val = a[i] + j * d;
if (val >= 0) st.insert(val);
}
}
vector<int> vals;
map<int, int> mp;
for (auto x:st) {
vals.push_back(x);
mp[x] = (int)vals.size() - 1;
}
int V = vals.size();
for (int i = 0; i < V; ++i) {
f[1][i] = INT_MAX;
}
f[1][mp[a[1]]] = 0;
for (int i = 2; i <= n; ++i) {
fill(f[i & 1], f[i & 1] + 1 + V, INT_MAX);
deque<int> q;
int k = 0;
for (int j = 0; j < V; ++j) {
while (q.size() && vals[q.front()] < vals[j] - d) q.pop_front();
while (k < V && vals[k] < vals[j] - d) ++k;
while (k < V && vals[k] <= vals[j] + d) {
while (q.size() && f[i & 1 ^ 1][q.back()] >= f[i & 1 ^ 1][k]) q.pop_back();
q.push_back(k);
k++;
}
f[i & 1][j] = f[i & 1 ^ 1][q.front()] + abs(a[i] - vals[j]);
}
}
cout << f[n & 1][mp[a[n]]] << endl;
}
\(O(n\log n)\) 解法
注意到我们的转移式相当于是对一个凸函数取区间 \(\min\)(相当于是对 \(0_{[l,r]}\) 做闵可夫斯基和)再加上另一个另一个凸函数,所以最终结果也一定是一个凸函数,所以我们可以直接维护凸包做。考场上卡住的点主要是在于,初始时的凸包是一个点(\(a_1\))为 \(0\),其他点都为 \(+\inf\),这个我不知道怎么处理。

浙公网安备 33010602011771号