PAT L3-012 水果忍者

来源:互联网 发布:ftp服务器端口默认为 编辑:程序博客网 时间:2024/05/02 06:12

PAT的训练题中比较复杂的一道,网上很难找到题解,于是我来发一波


目链接:https://www.patest.cn/contests/gplt/L3-012


如果存在某条直线符合题意,那么可以把它向下平移,直到碰到某个水果的下端端点,我把这个端点称为“卡子”。再旋转(顺逆时针皆可),即可碰到另一个“卡子”。这时候这条直线就是过两个水果端点的直线。

那么我只要枚举所有的水果下方的端点作为一个“卡子”,看看过这个点的直线有没有哪条能穿过所有水果。
先只考虑右边的水果。比如对于该水果右边的所有水果,如果这条直线能穿过他们,那么水果们的上方端点一定都在直线上方(或者在直线上),下方端点一定在直线下方(或者在直线下)。也就是说这条直线的斜率一定小于所有的右边水果上方端点和“卡子”连线的斜率,大于所有右边水果的下方端点和“卡子”连线的斜率。将右边的允许的最大斜率记为krmax,最小斜率记为krmin,那么符合的直线斜率k一定满足
krmax >= k >= krmin(仅仅是对于右边而言的)
对于左边的水果也同理,将左边的允许的最大斜率记为klmax,最小斜率记为klmin。
符合条件的直线斜率k一定满足
klmax >= k >= klmin(仅仅是对于左边而言的)
综合左右两边,符合条件的直线斜率k一定要满足
krmax >= k >= krmin && klmax >= k >= klmin
因此,如果
klmax < klmin || krmax < krmin || krmax < klmin  || klmax < krmin
就意味着这个“卡子”是行不通的,没有一条过他的直线能符合题意,枚举下一个水果吧。


优化1:将斜率全部表示为两个int组成的向量,斜率之间比大小用叉积即可,可以提高精度+节省时间。
优化2:优先枚举“短”的水果(即y1-y2小的水果),直觉嘛,“短”的水果成为“卡子”的几率更大一些。
(不上优化1,超时,上优化1,200ms过,再上优化2,95ms过)


AC代码:


#include <stdio.h>#include <algorithm>#define INF 99999999#define ll long longusing namespace std;struct line {int x, y1, y2;//y1 > y2}l[10005], id[10005];struct vec {int x, y;vec(int a = 0, int b = 0):x(a), y(b) {};};inline int cross(vec a, vec b) {//伪叉积,大于0就返回1,小于返回-1,等于返回0ll temp = (ll)(a.x)*(ll)(b.y) - (ll)(a.y)*(ll)(b.x);if (temp > 0) return 1;else if (temp == 0) return 0;return -1;}int n;vec krmax, krmin, klmax, klmin;inline bool cmp(const line& a, const line& b) {return a.x < b.x;}inline bool cmp2(const line& a, const line& b) {return (a.y1 - a.y2) < (b.y1 - b.y2);}int main() {scanf("%d", &n);for (int i = 1; i <= n; i++)scanf("%d%d%d", &l[i].x, &l[i].y1, &l[i].y2);sort(l + 1, l + n + 1, cmp);//先根据x坐标从小到大排个序for (int i = 1; i <= n; i++) {id[i].x = i;//这里的x就是记录竖线ID了(即记录是从左到右的第几根竖线)id[i].y1 = l[i].y1;id[i].y2 = l[i].y2;}sort(id + 1, id + n + 1, cmp2);//再根据y1-y2从小到大排个序,优先尝试用“短”的竖线下方的端点作“卡子”for (int idx = 1; idx <= n; idx++) {int i = id[idx].x;//尝试以从左到右的第i根竖线下方的端点作“卡子”krmax = vec(1, INF);krmin = vec(1, -INF);klmax = vec(-1, -INF);klmin = vec(-1, INF);for (int j = i + 1; j <= n; j++) {//向右找允许的最大斜率和最小斜率vec v1(l[j].x - l[i].x, l[j].y1 - l[i].y2);vec v2(l[j].x - l[i].x, l[j].y2 - l[i].y2);if (cross(krmax, v1) < 0) krmax = v1;if (cross(krmin, v2) > 0) krmin = v2;}for (int j = i - 1; j >= 1; j--) {//向左找允许的最大斜率和最小斜率vec v1(l[j].x - l[i].x, l[j].y1 - l[i].y2);vec v2(l[j].x - l[i].x, l[j].y2 - l[i].y2);if (cross(klmin, v1) > 0) klmin = v1;if (cross(klmax, v2) < 0) klmax = v2;}//判断以从左到右的第i根竖线下方的端点作“卡子”,是否可行,不可行直接continueif (cross(krmax, krmin) > 0) continue;if (cross(klmax, klmin) > 0) continue;if (cross(krmax, klmin) < 0) continue;if (cross(klmax, krmin) < 0) continue;//可行,分情况讨论if (cross(klmax, krmax) * cross(klmax, krmin) <= 0)printf("%d %d %d %d", l[i].x + klmax.x, l[i].y2 + klmax.y, l[i].x, l[i].y2);else if (cross(klmin, krmax) * cross(klmin, krmin) <= 0)printf("%d %d %d %d", l[i].x + klmin.x, l[i].y2 + klmin.y, l[i].x, l[i].y2);else if (cross(klmax, krmax) > 0 && cross(klmin, krmin) < 0)printf("%d %d %d %d", l[i].x + krmax.x, l[i].y2 + krmax.y, l[i].x, l[i].y2);else if (cross(krmax, klmax) > 0 && cross(krmin, klmin) < 0)printf("%d %d %d %d", l[i].x + klmin.x, l[i].y2 + klmin.y, l[i].x, l[i].y2);break;}return 0;}


1 0
原创粉丝点击