1. 程式人生 > >bzoj1863 [Zjoi2006]trouble 皇帝的煩惱

bzoj1863 [Zjoi2006]trouble 皇帝的煩惱

blog mes 滿足 二分答案 str logs 分答 感到 dig

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; }

bzoj1863 [Zjoi2006]trouble 皇帝的煩惱