Codeforces 1083E The Fair Nut and Rectangles
题目链接
https://codeforces.com/contest/1083/problem/E
题目大意
给你一个 $n$ 个左下角和右上角为 $(0, 0)$ 和 $(x_i, y_i)$ 的矩形,每个矩形有一个权值 $a_i$。问选出若干个矩形,最大化这些矩形的面积并与这些矩形的权值和的差。限制条件为,没有嵌套的矩形
题目分析
从限制条件中可以得知,任何两个矩形,横坐标大的那个纵坐标一定小,,反之亦然,且没有坐标相同的两个矩形。由此可以想到,首先对所有的矩形按照横坐标由小到大排序。之后考虑使用 $DP$ 来解决这道题。则可以推出递推式
$$f[i] = max\{f[j] – x_i y_j + x_i y_i – a[i]\} (1 \leq j < i)$$
但是此时时间复杂度是 $O(N ^ 2)$ 的。问题在于,每个新的矩形加入的时候 $x_i$ 都会影响递推式中的 $x_i y_i$ 这项与 $j$ 矩形有关的项。但注意到,$f[j] – x_i y_j$ 事实上是个一次的函数,后面有关 $i$ 矩阵的项为定值,因此可以考虑使用斜率优化。令 $y = (-y_j)x + (f[j])$,那么 $f[i]$ 的最大取值即为前面所有直线以及 $y = 0$ 直线中,在 $x = x_i$ 处取值最大的直线所对应的矩阵所转移来的。由于所有的直线的斜率具备单调性,可以使用双端队列来维护,求解即可。
代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <string> #include <algorithm> using namespace std; const int maxn = 1e6 + 5; int n, m, t; long long ans = 0; struct ex { long long x; long long y; long long a; }; ex p[maxn]; struct queue { long long k[maxn]; long long b[maxn]; int l, r; double getp(long long ka, long long ba, long long kb, long long bb){ return (double)(bb - ba) / (double)(ka - kb); } int push(long long kk, long long bb){ while(r - l + 1 > 1 and getp(k[r], b[r], kk, bb) > getp(k[r - 1], b[r - 1], k[r], b[r])){ r--; } r++; k[r] = kk; b[r] = bb; return 0; } int pop(long long x){ while(r - l + 1 > 1 and k[l] * x + b[l] < k[l + 1] * x + b[l + 1]){ l++; } return 0; } long long front(long long x){ if(r - l + 1 <= 0){ return 0; } return k[l] * x + b[l]; } queue(){ r = 0, l = 1; } }; queue q; long long read(){ long long x; scanf("%lld", &x); return x; } bool cmp(ex x, ex y){ return x.y > y.y; } int main(){ int i, j; long long tmp; n = read(); for(i=1;i<=n;i++){ p[i].x = read(); p[i].y = read(); p[i].a = read(); } sort(p + 1, p + n + 1, cmp); for(i=1;i<=n;i++){ q.pop(p[i].y); tmp = q.front(p[i].y) + p[i].x * p[i].y - p[i].a; tmp = max(tmp, p[i].x * p[i].y - p[i].a); ans = max(ans, tmp); q.push(-p[i].x, tmp); } printf("%lld\n", ans); return 0; }