洛谷1792 [国家集训队]种树(wqs二分) 洛谷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;
}