[JXOI2018]守卫
题意
link
\(n(\neq 5 \times 10^3)\) 个数 \(h_i\), 每个点可以放守卫,能覆盖他向左能看到的点,能看到定义为两点连线没有经过其它点即以下的位置。
求所有区间最小守卫数的异或和。
区间 dp
状态
\(f[l][r]\) 表示区间最小的守卫数。
初始状态
注意到区间最右端一定要放一个守卫。
即 \(f[r][r] = 1\)。
转移
考虑枚举目前区间 \([l, r]\) r 能看见的最左边的点 \(p\), 那么 \(p - 1\) 就是看不到的,因此再要 \(p - 1\) 或 \(p\) 处放守卫,也就是子区间 \(f(l, p), f(l, p - 1)\) 的子问题,
对于 \([p + 1, r]\) 也是一个子问题。
那 \(f[l][r] = \min_p\{f[l][p], f[l][p - 1]\} + f[p + 1][r]\)
分析
区间dp 状态 \(n ^ 2\), 转移可以做到 \(O(1)\)
那么时间复杂度就是 \(O(n ^ 2)\)。
代码
这个 sum 就是 \(f[p + 1][r]\) 只不过是顺路更新。
#include<bits/stdc++.h>
using namespace std;
//using ll = long long;
const int MAXN = 5010;
const int INF = 0x7fffffff;
const int mod = 1000000007;
const double eps = 1e-9;
template <typename T>
void Read(T &x) {
x = 0; T f = 1; char a = getchar();
for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
x *= f;
}
inline void add(int &a, const int &b) {
a = a + b;
if (a >= mod) a -= mod;
if (a < 0) a += mod;
}
inline int mul(const int &a, const int &b) {
return 1ll * a * b % mod;
}
int qpow(int a, int b) {
int sum(1);
while(b) {
if (b & 1) sum = mul(sum, a);
a = mul(a, a);
b >>= 1;
}
return sum;
}
int n;
int h[MAXN], f[MAXN][MAXN];
double slope(int a, int b, int c, int d) {
return 1.0 * (a - c) / (b - d);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> h[i];
}
int ans = 0;
for (int r = 1; r <= n; r ++) {
f[r][r] = 1;
ans ^= 1;
int sum = 1, p = 0;
for (int l = r - 1; l >= 1; l --) {
if (!p || slope(h[r], r, h[p], p) > slope(h[r], r, h[l], l)) {
if (p) sum += min(f[l + 1][p], f[l + 1][p - 1]);
p = l;
}
f[l][r] = sum + min(f[l][p], f[l][p - 1]);
ans ^= f[l][r];
}
}
cout << ans << endl;
return 0;
}