noip模拟赛 读
分析:感觉很像是贪心,但是直接贪找不到方法.一个暴力的想法是枚举最小步数,然后看每个指针能够覆盖到的位置,看看能不能覆盖到所有点.这个求最大覆盖就有点贪心的思想,因为给的ai,bi都是递增顺序的,考虑第i个指针,如果左边还有点没有覆盖到是一定要去覆盖的,剩下的步数可以一直往右走.这样的话有两种情况,要么是先把左边的覆盖了再往右走,要么是先尽量往右走,之后返回刚好把最左边的那个点给覆盖,讨论一下就可以了.如果左边没有点要覆盖,i尽量往右走就可以了.最后看被覆盖的指针是不是有m个即可.
由于ai,bi非常大,枚举最小步数要消耗太多的时间,需要进行优化.仔细揣摩一下这个过程就会发现这很像二分+check,因为枚举最小步数是从小到大枚举的嘛,所以符合单调性,二分即可.
枚举答案并判断是否可行的暴力做法可以优化为二分.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; int n, m; ll a[100010], b[100010], ans; bool check(ll x) { ll j = 1; for (int i = 1; i <= n; i++) { if (a[i] - b[j] > x) return false; ll can = 0; if (b[j] < a[i]) { ll rest = x - (a[i] - b[j]); can = max(b[j] + rest, a[i] + rest / 2); if (can < a[i]) can = a[i]; } else can = a[i] + x; while (j <= m && b[j] <= can) j++; if (j > m) return true; } return false; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); for (int i = 1; i <= m; i++) scanf("%lld", &b[i]); ll l = 0, r = 20000000010; while (l <= r) { ll mid = (l + r) >> 1; if (check(mid)) { ans = mid; r = mid - 1; } else l = mid + 1; } printf("%lld\n", ans); return 0; }