题解:P4563 [JXOI2018] 守卫
思路
首先观察到一个很重要的性质。
在题目中由于每个保镖只能看到自己的左边,所以对于区间 ,在 号亭子必然有一个保镖(不然最右边的保镖右边的亭子谁来看着)。
我们画出图分析,发现在 左边最后一个能被看到的亭子(以下记 为这个亭子的位置)的左边就已经成为了一个与其他子区间完全独立的区间,所以考虑动态规划。
状态表示: 表示在区间 之间,最少需要放多少个保镖。
首先我们知道,当 的时候,。然后我们发现,在得到状态 时,我们不仅需要考虑 这个区间,还需要考虑 这个区间,于是我们考虑维护 来求出当前 这个区间的保镖数,我们的 需要从大往小枚举。
枚举 的顺序是从大到小的,也就是从右到左,那么 就会被更新。 会被更新当且仅当连 与 点的斜率小于连 到 的斜率(这里读者可以画图来理解)。
首先考虑 的计算,当 被更改时,,其他情况不变。特别的,当区间大小为一时, 为 。
接下来,因为我们需要知道 的子区间 与 的值,而 与 是小于 的,所以我们要把 从小到大枚举,这样我们就可以愉快地转移状态了。
状态计算/转移:。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e3 + 10;
int f[N][N], h[N], n, ans;
inline double line(int x, int y) { return (double)(h[x] - h[y]) / (x - y); }
inline bool stop(int x, int y, int z) { return line(z, x) > line(z, y); }
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> h[i];
for (int r = 1; r <= n; r++) {
ans ^= (f[r][r] = 1);
int sum = f[r][r], p = 0;
for (int l = r - 1; l >= 1; l--) {
if (!p || !stop(l, p, r)) {//sum只维护了之前的[p+1,r],此时p的位置改变,于是需要更新 sum,即加上 [l+1,p] 或 [l+1,p-1]。
if(p) sum += min(f[l + 1][p], f[l + 1][p - 1]);// ! 注意当 p 为 0 时,也会进入上个分支,此时 f[l+1][p-1] 会出现访问 f[l+1][-1] 的情况,需要特判掉。
p = l;
}
ans ^= (f[l][r] = min(f[l][p - 1], f[l][p]) + sum);
}
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号