bzoj1863 [Zjoi2006]trouble 皇帝的烦恼

Description

经过多年的杀戮,秦皇终于统一了中国。为了抵御外来的侵略,他准备在国土边境安置 \(n\) 名将军。不幸的是这 \(n\) 名将军羽翼渐丰,开始展露他们的狼子野心了。他们拒绝述职、拒绝接受皇帝的圣旨。秦皇已经准备好了秘密处决这些无礼的边防大将。不过为防兵变,他决定先授予这些将军一些勋章,为自己赢得战略时间。将军们听说他们即将被授予勋章都很开心,他们纷纷上书表示感谢。第 \(i\) 个将军要求得到 \(a_i\) 枚不同颜色的勋章。但是这些将军都很傲气,如果两个相邻的将军拥有颜色相同的勋章他们就会认为皇帝不尊重他们,会立即造反(编号为i的将军和编号为 $ i+1$的将军相邻;因为他们驻扎的边境可以类似看成一个圆形,所以编号 \(1\) 和编号 \(n\) 的将军也相邻)。皇帝不得不满足每个将军的要求,但对他们的飞扬跋扈感到很气愤。于是皇帝决定铸造尽量少种类的勋章来满足这些狂妄者的要求。请问他至少要铸造多少种颜色的勋章?

Input

第一行有一个整数 \(n(1\leq n\leq 20000)\)。接下来 \(n\) 行每行一个整数 \(a_i\),表示第 \(i\) 个将军要求得到多少种勋章。\((1\leq a_i \leq100000)\) 输出一个整数,即最少需要多少种勋章。

Output

4 2 2 1 1

Sample Input

4

Solution

这应该是我做过的最巧妙的题之一了。显然应该考虑二分答案,但问题是怎么\(O(n)\) \(check\)。正解是用一个巧妙的 \(dp\)\(check\)\(mn[i]\)表示编号为 \(i\) 的人最少和编号为 \(1\) 的人有多少个重复的\(mx[i]\) 表示编号为 \(i\) 的人最多和编号为 \(1\) 的人有多少个重复的。假如现在二分到 \(x\) ,那么:

\(mx[i] = min(a[i], a[i] - mn[i - 1])\)
\(mn[i] = max(0, a[i] - (x - a[i - 1] - a[1] + mx[i - 1]))\)

这样最后检查一下 \(mn[n]\) 是否为 \(0\) 就可以了。

#include<bits/stdc++.h>
using namespace std;

#define N 300001
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define drp(i, a, b) for (int i = a; i >= b; i--)
#define fech(i, x) for (int i = 0; i < x.size(); i++)
#define ll long long

inline int read() {
	int x = 0; char ch = getchar(); while (!isdigit(ch)) ch = getchar();
	while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x;
}

int n;
ll a[N], mx[N], mn[N];

bool check(int x) {
	mx[1] = mn[1] = a[1];
	rep(i, 2, n) mx[i] = min(a[i], a[1] - mn[i - 1]), mn[i] = max(0ll, a[i] - (x - a[i - 1] - a[1] + mx[i - 1]));
	return !mn[n];
}

int main() {
	n = read(); ll l = 0, r, ans; rep(i, 1, n) a[i] = read(), l = max(l, a[i] + a[i - 1]); r = l << 1;
	while (l <= r) {
		int mid = l + r >> 1;
		if (check(mid)) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	cout << ans; return 0;
}
posted @ 2018-02-05 09:52  aziint  阅读(133)  评论(0编辑  收藏  举报
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.