POJ2008 数学几何+递推
题意
有牛牛N条,分别给出他们的身高(h)体重(w),给出常量A、B、C。在所有牛牛中找出一个包含最多数量牛牛的组,这个组所有牛牛都满足公式:A(H-h) + B(W-w) <= C
其中h代表这个组牛牛身高的最小值,w代表体重最小值。
思路
开始以为h、w代表的是所有牛的最小值,感觉这道题好水哦-_-||!后面理解题意后就像参考博客的博主说的一句话一样:刚一看题就知道做不出来了
Step1:
首先解题的初步思路是:把所有牛牛都转换成点。
然后根据公式把问题转换为一个直角三角形内最多包含多少点?
这是解题起点,然后对数学不敏感真转换不过去吖=-=!
为什么?
虽然思路这么一说感觉很有道理,但是其实也不是很搞的明白为什么可以这样!就感觉:嗯嗯嗯,对的就该这样,但为什么可以这样?
首先当把所有牛都转换成点放入坐标轴后,如下图:
再看公式:A(H-h) + B(W-w) <= C
∴点坐标(W,Y) ∵A、B、C为常量,而对某组牛来说,h、w也是常量,∴公式的变量就只有H、W ∴公式此时就变成了一个点范围公式,这个范围就是个直角三角形。
为H、W必须大于一个常量,也就是h,w。同时H、W间经过某个公式又要小于某个值。这些是不是就把H、W的范围限制在了一个直角三角形内?
直角三角形边长是固定的cw = C/B,ch = C/A
找到直角三角形两个锐角点,也就是整个直角三角形所包含的无数点中y(h)、x(w)最大的两个点
这两个点坐标可以定为(H,w)、(h,W)
而直角三角形两条边长度就是H-h,W-w代入公式,两条边长度就求出来了,据说是初中数学???
看到这里,已经明了很多,题目意思就是求:一个固定直角三角形在坐标轴上能包含最多的点?这个直角三角形两边上必须含有点,也就是组中身高或体重最小的牛牛。
如上图所示:一个点能跟另一些点能组成相同形状(满足公式)但不同位置的直角三角形,而求的就是所有点跟其他点组成的满足条件的直角三角形中包含点最多的点的个数。
到这里,基本已经可以用朴素做法是O(N3)**了,但是标准做法是**O(N2),也就是说从现在开始,才是精彩部分。
Step2
暴力不可取,思路分析清楚了就该如何优化算法了。
求一个三角形内的点数,可以分解为一个矩形内的点数减去一个梯形内的点数。
梯形并不重要,请不要在乎它!
这个思想好的是:求矩形内的点数对我们十分友好。
有了这个思想,我们将所有点进行y(H)轴方向的排序,然后从上到下对点进行遍历。
当我们依次从上到下求三角形内点时,上下三角形含有下图关系:
也就是加上一个矩形再减去一个平行四边形。(图中灰色的两个图形)
∴增加的点就是图中y(H)轴那个灰色小矩形,我们很好求得,但是平行四边形呢?
再次看公式:A(H-h) + B(W-w) <= C,拆分得:AH+BW <= Ah+Bw+C
∴AH+BW的大小就是表示点在直角三角形中的整体位置,大表示偏向右上,小就是在左下,其中:右、上两个方向所占的比值就是斜率。
有点啰嗦了,只是把自己所有分析思路都写上去了,直接看代码吧。
代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Scanner;
/**
* @Author Yuri
* @Date 2020/11/27 17:06
* @Version 1.0
* @Description: POJ2008
*/
public class Main {
static Scanner in = new Scanner(System.in);
static int n, A, B, C, ch, cw, box, slash, cnt;
static ArrayList<Cow> cows = new ArrayList<Cow>();
static ArrayList<Cow> sortH, sortK = new ArrayList<Cow>();
static int ans = 0;
static class Cow {
int h, w, k;
Cow(int h, int w) {
this.h = h;
this.w = w;
this.k = A * h + B * w;
}
@Override
public String toString() {
return "Cow{" +
"h=" + h +
", w=" + w +
", k=" + k +
'}';
}
}
public static void update(int h, int w) {
int k;
while (box < n && sortH.get(box).h >= h) { //增加的矩形部分
if (sortH.get(box).w >= w && sortH.get(box).w <= w + cw) {
cnt++;
}
box++;
}
k = A * h + B * w + C;
//AH+BW <= Ah+Bw+C 减少的平行四边形部分
while (slash < n && sortK.get(slash).k > k) {
if (sortK.get(slash).w >= w && sortK.get(slash).w <= w + cw) {
cnt--;
}
slash++;
}
if (cnt > ans) {
ans = cnt;
}
}
public static void alg(int i) {
int h = sortH.get(i).h;
int w = sortH.get(i).w;
box = slash = cnt = 0;
//遍历和点i能组成满足公式的所有直角三角形
while (i < n && sortH.get(i).h >= h - ch) {
if (sortH.get(i).w >= w && sortH.get(i).w <= w + cw) {
update(sortH.get(i).h, w);
}
i++;
}
}
public static void main(String[] args) {
n = in.nextInt();
A = in.nextInt();
B = in.nextInt();
C = in.nextInt();
for (int i = 0; i < n; i++) {
cows.add(new Cow(in.nextInt(), in.nextInt()));
}
ch = C / A;
cw = C / B;
sortH = (ArrayList<Cow>) cows.clone();
sortK = (ArrayList<Cow>) cows.clone();
Collections.sort(sortH, new Comparator<Cow>() {
@Override
public int compare(Cow o1, Cow o2) {
return o2.h - o1.h;
}
});
Collections.sort(sortK, new Comparator<Cow>() {
@Override
public int compare(Cow o1, Cow o2) {
return o2.k - o1.k;
}
});
for (int i = 0; i < n; i++) {
alg(i);
}
System.out.println(ans);
}
}