暑假和小学神仙们集训的时候发过这道题,一直不知道如何去解决。一个月前crazydave大佬给我简述过二分答案的原理,而蒟蒻我今天才搞定这些。
我们先来看例题:洛谷P2678
这是一道NOIP提高组的简单题(我太菜了),主要是通过枚举答案,在每次枚举时检测能不能符合要求即可。先看代码:
// P2678.cpp #include <iostream> using namespace std; const int maxn = 500020; int L, N, M; int D[maxn]; bool check(int num) { int last = 0; int cnt = 0; for (int i = 0; i <= N; i++) if (D[i] - last < num) cnt++; else last = D[i]; if (cnt > M) return false; return true; } void solve() { int left = 0; int right = L; int ans = 0; while (left <= right) { int mid = (left + right) / 2; if (check(mid)) left = mid + 1, ans = mid; else right = mid - 1; } cout << ans; } int main() { cin >> L >> N >> M; for (int i = 0; i < N; i++) cin >> D[i]; D[N] = L; solve(); return 0; }
check函数就是验证答案num(也就是我们枚举出来的答案)是否符合要求,在这里便是检测当最小距离为num时,移走的石子会不会超过限制。如果超过了,返回false,二分便会把区间调为[left,mid-1],以降低最大值来试探限制;没有超过,则返回true,二分把区间调整为[mid+1,right],以追求更大的最大值。
抽象化分析结果
准备枚举一道题答案之前,请考虑二分答案。如果可以二分答案,那么时间复杂度会极度降低,从O(n^2)转变为O(n log n)。二分答案的结构就是二分代码和检验答案正确性代码。如果能通过正确性检查,那么追求更大(小)的答案并重新枚举;如果无法通过,则把区间调整,在较小(大)区间内追求更大(小)的答案。亦复如此。