这里使用非常朴素的O(n3logn)+剪枝过了这题.
N的范围才到500,然而坐标的范围让人刚好没法开二维数组,<<进阶>>一书上有前缀和的标签,我没想出来如何实现.
求最小边长,使得其中含有至少C单位三叶草,这是二分答案.
现在需要一个检查边长为p的正方形能否满足条件的函数,他要对这个正方形所有可能的左上角坐标进行检查.
哪些坐标是可能的?可以很清楚地看出来那些所在一整行或者一整列都没有三叶草的点根本不需要考虑,总有更优的行或列可以选择.
所以把输入所有三叶草的x,y坐标存下来,他们相互任意组合构成所有的可能坐标.这种对应产生不多于n2个点需要检查.
现在如何检查已知左上角坐标和边长的正方形内有多少三叶草?与其逐个检查正方形内的点,不如逐个检查三叶草的坐标.
与其逐个检查三叶草的坐标,不如将三叶草按坐标排序后从正方形边界坐标开始检查,如此便把每个正方形检查复杂度降到了至多O(n).
现在还是会超时,想了一会又来了个剪枝,如果在检查所有三叶草中途发现已经在正方形内的数量加上还没检查的三叶草数量已经小于c了,那么显然不行,直接跳到下一个正方形.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> using namespace std; typedef pair<int, int> PII; int n, c, x[510], y[510], idx, idy; PII s[510]; bool cmpX(PII a, PII b) { return a.first < b.first; } bool check(int p) { for (int i = 1; i <= idx; i++) { PII tmp(x[i], -1); int begin = lower_bound(s + 1, s + n + 1, tmp, cmpX) - s; for (int j = 1; j <= idy; j++) { int sx = x[i], sy = y[j], ct = 0; for (int k = begin; s[k].first <= sx + p - 1 && k <= n; k++) if (s[k].second >= sy && s[k].second <= sy + p - 1) { ct++; if (ct >= c) return true; if(ct + n - k < c) break; } } } return false; } int main() { // freopen("in.txt", "r", stdin); bool bx[10010], by[10010]; memset(bx, 0, sizeof(bx)); memset(by, 0, sizeof(by)); scanf("%d%d", &c, &n); int a, b; for (int i = 1; i <= n; i++) { scanf("%d%d", &a, &b); s[i] = {a, b}; bx[a] = true; by[b] = true; } s[0] = {-1, -1}; for (int i = 1; i <= 10001; i++) { if (bx[i]) x[++idx] = i; if (by[i]) y[++idy] = i; } sort(s + 1, s + n + 1, cmpX); int l = 0, r = 10001; while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } printf("%d ", l); return 0; }
后来我才知道用pair去sort可以按x第一关键字排序,按y第二关键字排序,这样就可以用lower_bound找到x和y的起点边界,应该会再快一点.