洛谷1792 [国家集训队]种树(wqs二分)
题目描述
A 城市有一个巨大的圆形广场,为了绿化环境和净化空气,市政府决定沿圆形广场外圈种一圈树。
园林部门得到指令后,初步规划出 n 个种树的位置,顺时针编号 1 到 n。并且每个位置都有一个美观度 Ai ,如果在这里种树就可以得到这Ai的美观度。但由于A城市土壤肥力欠佳,两棵树决不能种在相邻的位置( i 号位置和 i+1 号位置叫相邻位置。值得注意的是 1 号和 n 号也算相邻位置!)。
最终市政府给园林部门提供了 m 棵树苗并要求全部种上,请你帮忙设计种树方案使得美观度总和最大。如果无法将 m 棵树苗全部种上,给出无解信息。
数据范围
(1 le n le 200000)
解题思路
种树要浇忘情水
一眼看上去就是 dp 啊QAQ
先解决环对后效性的干扰,考虑 1 号点种不种树,种的话就是 ([3, n-1]) 一条链,不种就是 ([2, n]) 的一条链
考虑暴力 dp,(f[i][j]) 表示前 i 个位置,种了 j 棵树的最大值是多少,有
[f[i][j] = max(f[i-1][j], f[i-2][j-1] + a[i])
]
显然过不去,不难臆想出答案随着种树的棵数变化是单峰的,考虑忘情水二分
然后就没了~
template <typename T>
inline void Mx(T &x, T y) { x < y && (x = y, 0); }
const int N = 200050;
struct DP {
ll val, cnt;
bool operator < (DP b) {
return val < b.val;
}
DP operator + (DP b) {
return (DP){val + b.val, cnt + b.cnt};
}
}f[N];
bool check(int del, int n, int m, int *a) {
f[1] = (DP) {a[1] + del, 1}; Mx(f[1], f[0]);
for (int i = 2;i <= n; i++) {
f[i] = f[i-2] + (DP) {a[i] + del, 1};
Mx(f[i], f[i-1]);
}
return f[n].cnt >= m;
}
ll work(int n, int m, int *a) {
ll l = -200000, r = -l;
while (l <= r) {
ll mid = (l + r) >> 1;
if (check(mid, n, m, a)) r = mid - 1;
else l = mid + 1;
}
check(l, n, m, a); //此处注意要用l, 因为我更新的时候 cnt 可能取不到 m 而偏大
return f[n].val - l * m;
}
int a[N];
int main() {
int n, m; read(n), read(m);
if (m > n/2) return puts("Error!"), 0;
for (int i = 1;i <= n; i++) read(a[i]);
write(max(a[1] + work(n - 3, m - 1, a + 2), work(n - 1, m, a + 1)));
return 0;
}