CF#303B Rectangle Puzzle II 数学分析
题意:给定一个n*n的矩形框,也就是限制在一个(0,0)到(n,m)的平面区域内,求出一个最大的矩形,该矩形面积要求最大,且长和宽的比要为a/b,并且该矩形需包含一个限制区域内的一个点,该矩形的中心应该离这个点越近越好。输出左下角和右上角的坐标。如果在满足所有要求的情况相同近的话输出坐标字典序最小的一种情况。
解法:初读起来,这题的限制条件颇多,做的过程中确实有许多地方需要注意,那么我们来一步步解决这个问题:
1.首先假设这个矩形的的长(投影到x轴上的量)为sl, 宽为sw(投影到y轴上的量)。那么依据题意有sl / sw = a / b。将方程变形有b*sl - a*sw = 0的形式,可以用一个通式表示这个式子即为:A*x + B*y = C。是否非常的熟悉,这就是一个线性方程整数解的求解,使用扩展欧几里得定理求解。由于C=0,所以该方程总是有解。那么推导出的通解sl = k1*a/gcd(a,b),sm = k2*b/gcd(a, b),我们只需要计算一个满足0<=sl<=n 且 0<=sw<=n的最大的k即可,这样也就得到了满足该矩形的最大的情况。
2.设要包含的点为s(xs, ys),接下来我们就要考虑这个最大的矩形和点s的位置关系。假设这个点s能够成为这个矩形的中心点,且计算出s成为中心点后各个顶点的坐标。先来考虑这个点如何成为一个中心以及如何计算顶点坐标:
A.sl为偶数,那么左边两个顶点的横坐标为xl = xs - sl / 2,右边两个顶点左边为xr = xs + sl。sw为偶数时,下面两个顶点yd = ys - sw / 2, 上部yu = yb + sw。
B.sl为奇数,那么由于xs只能够使得左右两边的长度之差为1来保持最靠近中心,又因为要输出的坐标字典序最小,因此果断选择xl = xs - sl/2-1, xr = xl + xs;同理yd = ys - sw/2-1, yu = yd + sw;
综上:xl = x - (sl+1)/2, xr = xl + sl; yd = y - (sw+1)/2, yu = yd + sw;
3.接下来就是要调整好这个矩形了,这会是一个贪心的过程由于计算出来的坐标可能不满足都在限制区域内,因此调整是免不了的。考虑到如果顶点全部在限制域内则直接输出,否则的话可以容易证明将超出去的顶点移动到限制域内的边界点上以最优的,因为如果在往里面移动将使得s离中心越来越远。
代码如下:
#include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <algorithm> using namespace std; int n, m, x, y, a, b; int main() { int sw, sl; while (scanf("%d%d%d%d%d%d", &n,&m,&x,&y,&a,&b) != EOF) { int d = __gcd(a, b), k; // 以下结果由扩展gcd推导得出 k = min(n/(a/d), m/(b/d)); sl = 1LL * k * a/d; // 小心溢出 sw = 1LL * k * b/d; // 此时应该以(x,y)为中心做一个假设 // 当然如果长度为sl时没有办法被x分成两个部分,此时由于字典序的限制那么让左边的长度较长 // 也即然左下角坐标x1尽可能的小,对于y轴也是同样的处理 int xl, yd, xr, yu; xl = x - (sl+1)/2, xr = xl + sl; yd = y - (sw+1)/2, yu = yd + sw; // 这个是以(x,y)为中心点或者是偏离中心点最近且字典序最小的一个点对 // 以下是对超出部分的一个贪心修正 xl = max(0, xl), xr = xl + sl; xr = min(n, xr), xl = xr - sl; yd = max(0, yd), yu = yd + sw; yu = min(m, yu), yd = yu - sw; printf("%d %d %d %d\n", xl, yd, xr, yu); } return 0; }