Uva LA 3177 - Beijing Guards 贪心,特例分析,判断器+二分,记录区间内状态数目来染色 难度: 3
题目
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1178
题意
圆桌上有n个人,每个人要求a_i种不同的礼物,相邻两个人的礼物不能重复,问有至少要准备多少种礼物
思路
如刘书
1. 明显,若n=1,直接输出a[0]
2. 若n为偶数,则可以形如ABABAB,直接取最大的两个相连之和
3. 若n为奇数,则可以转化为二分判断问题。
但如何判断x种礼物能否满足要求呢?n * r状态约为1e10,明显太大不能承受。
假设送第0个人,0~a[0]-1这a[0]种礼物,那么第n-1个人明显不能再拿到这a[0]种礼物了。
要找到一个贪心的原则,
由于第n-1个人编号为偶数,那么,偶数的人尽量取a[0]~x-1这些礼物,奇数的人尽量取0~a[0]-1这些礼物就可以。
但是这样如果记录每个状态时还是会出现麻烦。
那么,直接令r[i]记录第i个人拿到0~a[0]-1范围内礼物的数目,l[i]为a[0]~x-1这些礼物的数目,就能记录其状态的同时保持计算传递。
感想
1. 一开始没有注意到如果是奇数,如ABABA这种情况就会挨在一起了
2. 后来没有想到可以在延展过程中逐渐替换掉一小部分结果,而是以为要直接找个第三个最小元素的换掉,这样结果就太大了
3. 最后是没有注意到特例n=1的存在。
代码
#include <algorithm> #include <cassert> #include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <map> #include <queue> #include <set> #include <tuple> #define LOCAL_DEBUG using namespace std; const int MAXN = 1e5 + 4; int a[MAXN]; int r[MAXN], l[MAXN]; int n; bool check(int kindNum) { int rlimit = a[0]; int llimit = kindNum - rlimit; r[0] = rlimit; l[0] = 0; for (int i = 1; i < n; i++) { if (i & 1) { r[i] = min(rlimit - r[i - 1], a[i]); l[i] = a[i] - r[i]; if (l[i] > llimit - l[i - 1]) { assert(false); return false; } } else { l[i] = min(llimit - l[i - 1], a[i]); r[i] = a[i] - l[i]; if (r[i] > rlimit - r[i - 1]) { assert(false); return false; } } } return r[n - 1] == 0; } int main() { #ifdef LOCAL_DEBUG freopen("C:\\Users\\Iris\\source\\repos\\ACM\\ACM\\input.txt", "r", stdin); //freopen("C:\\Users\\Iris\\source\\repos\\ACM\\ACM\\output.txt", "w", stdout); #endif // LOCAL_DEBUG for (int ti = 1; scanf("%d", &n) == 1 && n; ti++) { int ans = 0; for (int i = 0; i < n; i++) { scanf("%d", a + i); if (i)ans = max(ans, a[i] + a[i - 1]); } ans = max(ans, a[n - 1] + a[0]); if (n == 1) ans = a[0]; else if (n & 1) { int mxAns = 2 * ans + 1; while (ans < mxAns) { int mid = (ans + mxAns) >> 1; if (check(mid)) { mxAns = mid; } else { ans = mid + 1; } } } printf("%d\n", ans); } return 0; }