poj 1755

题目概述

铁人三项分游泳,骑车,跑步三部分,最后总用时最短者获胜,现有N个选手,每个选手在三部分中的速度分别是u,v,w,假设你是(被收买的)主办方,对于每个选手,问你能否通过合理设置三部分的长度使其获胜
每部分长度不能是0,并列获胜不算获胜

时限

1000ms/3000ms

输入

第一行正整数N,其后N行,每行3个正整数u,v,w,输入到EOF为止

限制

1<=N<=100;1<=u,v,w<=10000

输出

每行一个字符串,若可使输入中该行所代表的选手获胜,则为Yes,否则为No

样例输入

9
10 2 6
10 7 3
5 6 7
3 2 7
6 2 6
3 5 7
8 4 6
10 4 2
1 8 7
2
1 1 10000
2 2 9999
64
9998 10000 9999
9999 9999 9998
10000 9997 9997
9997 9998 9999
9998 9997 9997
9998 10000 10000
9997 9997 9998
9998 9999 9999
10000 9999 9999
9998 10000 9998
9997 9998 9998
9998 9998 9999
9997 10000 9999
10000 9999 9997
10000 9998 9998
9998 9999 9998
9999 10000 9997
10000 10000 10000
9999 9997 9997
9997 9999 9997
9997 9999 10000
9999 9998 10000
10000 9998 9997
9999 9998 9998
9997 10000 9997
9997 9997 9997
9999 9999 9999
9998 9997 9999
10000 9997 9999
9999 9997 10000
9997 9999 9998
10000 9998 10000
9999 9998 9997
9997 9997 9999
9997 9998 9997
9997 10000 9998
9999 9997 9999
9997 9998 10000
9998 9997 9998
9998 9998 10000
10000 9999 9998
9997 9997 10000
9998 9998 9997
9998 9999 10000
9999 9997 9998
9997 10000 10000
9998 10000 9997
9999 10000 9999
9998 9997 10000
10000 9997 9998
10000 10000 9998
9998 9998 9998
10000 10000 9997
9999 9998 9999
10000 9999 10000
9999 9999 9997
9999 9999 10000
10000 9998 9999
9999 10000 9998
9998 9999 9997
9999 10000 10000
10000 9997 10000
9997 9999 9999
10000 10000 9999
100
1020 1090 1080
1000 1080 1100
1000 1090 1100
1010 1010 1090
1090 1080 1010
1060 1070 1040
1080 1050 1020
1040 1010 1060
1090 1000 1010
1070 1010 1030
1060 1010 1040
1070 1080 1030
1040 1070 1060
1020 1000 1080
1080 1070 1020
1040 1000 1060
1040 1060 1060
1030 1080 1070
1050 1020 1050
1030 1010 1070
1020 1060 1080
1010 1060 1090
1050 1030 1050
1060 1040 1040
1090 1030 1010
1050 1080 1050
1020 1050 1080
1070 1050 1030
1030 1050 1070
1010 1020 1090
1040 1050 1060
1080 1080 1020
1020 1020 1080
1000 1050 1100
1000 1020 1100
1070 1030 1030
1070 1040 1030
1020 1070 1080
1020 1080 1080
1080 1090 1020
1050 1070 1050
1040 1040 1060
1080 1040 1020
1050 1040 1050
1080 1020 1020
1030 1040 1070
1090 1040 1010
1020 1030 1080
1080 1060 1020
1070 1020 1030
1060 1030 1040
1000 1040 1100
1060 1080 1040
1010 1040 1090
1090 1090 1010
1090 1060 1010
1000 1060 1100
1030 1090 1070
1070 1060 1030
1080 1000 1020
1010 1000 1090
1050 1060 1050
1090 1070 1010
1010 1030 1090
1010 1080 1090
1010 1050 1090
1000 1070 1100
1010 1070 1090
1000 1010 1100
1030 1030 1070
1060 1090 1040
1090 1050 1010
1080 1010 1020
1010 1090 1090
1070 1090 1030
1020 1010 1080
1030 1020 1070
1060 1050 1040
1060 1060 1040
1050 1050 1050
1050 1090 1050
1040 1090 1060
1000 1030 1100
1070 1070 1030
1040 1020 1060
1050 1000 1050
1030 1070 1070
1060 1000 1040
1020 1040 1080
1070 1000 1030
1080 1030 1020
1040 1030 1060
1050 1010 1050
1090 1020 1010
1030 1060 1070
1060 1020 1040
1090 1010 1010
1040 1080 1060
1030 1000 1070
1000 1000 1100

样例输出

Yes
Yes
Yes
No
No
No
Yes
No
Yes
Yes
Yes
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
Yes
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
Yes
No
Yes
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
Yes
No
No
No
No
No
No
No
No
No
No
No
No
No
No
Yes
No
No
Yes
No
No
No
No
No
No
No
No
No
No
No
No
Yes
No
No
Yes
Yes
No
No
No
No
No
Yes
Yes
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No
No

讨论

计算几何,半平面交,这个题无论是蓝白书上或是国家队02年李澎煦的论文上都有简要说明,思路十分巧妙,设三段路每段长度分别为x,y,z,则某人总用时是

xu+yv+zw
若要严格获胜,则要使其严格小于其他每个人的总用时
xui+yvi+zwi<xuj+yvj+zwj,(ij)
其中i是当前考虑的人,j是其他人,将不等号右边都搬到左边,就是
(1ui1uj)x+(1vi1vj)y+(1wi1wj)z<0,(ij)
三个未知量有点多,既然求的是可行性问题,实际上也就是问有没有解而已,既然三段都不能为0,那左右同除z也无妨
(1ui1uj)xz+(1vi1vj)yz+(1wi1wj)<0,(ij)
然后将xzyz看做X和Y,原来的系数看做A,B,C,就成了下面的形式
AX+BY+C<0
这是很多人都熟悉的半平面交表示方法(额不熟悉),那么接下来就是求N-1个如此形式的半平面交了,如果最后的交还存在,而且没有由面退化,那么此人就可以获胜,顺便一提,如此的复杂度是O(n3),枚举所有人,以及半平面交自身的开销,但是由于剪枝的存在,实际速度非常快

到这里,基本思路已经明确了,下面就是实现部分
第一步,把给出的u,v,w转化为半平面交A,B,C,从上面的式子可以看到需要两个除法,然后作差,为了减小误差,应尽量避免除法,利用下面的公式,改为乘法运算

1ui1uj=ujuiuiuj

第二步,半平面交点集的初始化,一般的思路也是分两种,第一种是从无穷大(利用4个非常大的边界)开始切割,第二种是从既有图形上开始切割,后者是额一直用的方法,但是这个题没有给点,因而也不存在既有图形,只能选第一种,定义出边界,顺便也完成三段都不能为0的处理,令可行域坐标全正
第三步,剪枝,考虑到如果有一个人在三项上都不慢于当前考虑的人,那么无论怎么设计赛道,当前考虑的人总是无法超过那个人,因而没有可能获胜,基于如此思想,代入到之前所求公式,即体现为A,B,C三者全非负(因为上面的公式取的倒数,表示的是用时),若出现此情况,直接就是No,省去了半平面交的开销,也避免过于接近的两个数的运算导致的误差
第四步,计算,这部分的实现也与以往大不相同,会在代码中体现
第五步,判定,常见的判定半平面交可行域是否存在也是两种方法,其一,判断围成图形的面积,一般选从无穷大切割的会用,因为不会碰到无界的情况,其二,判断半平面交点集中剩余点的数量,这个在求交点的时候需要函数高度可靠,这两种方式的选择在这个题上会对精度需求产生巨大影响,选用求面积时,由于极端数据的存在(第2组样例),其可行域面积为2e-16,因此EPS需要1e-16级才能将其视为存在可行域,选用判断点个数时,首先要注意到是严格获胜,不过由于剪枝的存在,这些极端的情况都被剪掉了,EPS只需要满足剪枝和中间计算的需求,这个需求是1e-8级,大了很多
额在讨论版和博客里看了一些代码,基本上EPS在大于1e-16级的都是选用求点个数的方法,不过也有少数歪打误撞用求面积法也过了的,也是各有瑕疵,不便一一列举
这次由于需要写公式,为了美观,现学了一点latex的排版,感觉还不错
数据方面,没有再全部列举,只是挑选了有代表性的四组挂上了
题解代码中不知到为什么用的1e-16,似乎是忘了改过来,不过速度反而比1e-8快一些

题解状态

200K,0MS,C++,1955B

题解代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 300
#define memset0(a) memset(a,0,sizeof(a))
#define EPS 1e-16//开1e-8也能过 但最快也要16ms

struct Pt//point 点的结构
{
    double x, y;
    Pt() {}
    Pt(double x, double y) :x(x), y(y) {}//由于需要四个边界的点 而poj又不支持c++11的大括号初始化 只能以此法
}pts[MAXN], pt1[MAXN], pt2[MAXN];//点的原始数据 以及半平面交的两个辅助数组(点集)
int hpt1, hpt2;//halfplane_point 每个点集中点的个数
int N;//人数 或者说半平面数+1
double u[MAXN], v[MAXN], w[MAXN];//三个速度值
int signal(double a)//符号函数
{
    if (abs(a) < EPS)
        return 0;
    return a > 0 ? 1 : -1;
}
Pt point_of_intersection(Pt &a, Pt &b, double A, double B, double C)//求交点 由于这次半平面都是解析式形式 因而不再是8个double 而是常见的两点一解析式的形式 A,B,C是半平面解析式的三个系数 a,b是两点 构成一条线
{
    double Sabc = A*a.x + B*a.y + C, Sabd = A*b.x + B*b.y + C;
    return Pt((Sabc*b.x - Sabd*a.x) / (Sabc - Sabd), (Sabc*b.y - Sabd*a.y) / (Sabc - Sabd));//主体部分仍然是定比分点公式 不过简写了一点
}
void fun()
{
    for (int p = 0; p < N; p++)
        scanf("%lf%lf%lf", &u[p], &v[p], &w[p]);//input
    for (int p = 0; p < N; p++) {//枚举当前考虑的人
        pt1[0] = Pt(0, 0);
        pt1[1] = Pt(INF, 0);
        pt1[2] = Pt(INF, INF);
        pt1[3] = Pt(0, INF);//四个无穷大平面 顺手解决赛道不能为0的问题
        hpt1 = 4;//初始只有上面4个点
        for (int i = 0; i < N; i++) {//枚举一个对手
            if (i == p)//自己和自己没什么可比性
                continue;
            double A = (u[i] - u[p]) / (u[i] * u[p]);
            double B = (v[i] - v[p]) / (v[i] * v[p]);
            double C = (w[i] - w[p]) / (w[i] * w[p]);//求出能战胜该人的半平面的三个参数 由于自己作为被减数 因而半平面的逆时针方向为可以战胜该人的部分
            hpt2 = 0;//初始化点集2为空
            if (signal(A) >= 0 && signal(B) >= 0 && signal(C) >= 0) {//剪枝 如果自己在三项上用时不比比人家少 显然无法获胜
                printf("No\n");//output
                goto bp1;//为了避免重复输出而直接跳过去
            }
            for (int u = 0; u < hpt1; u++) {//下面是半平面交计算的主体部分 几乎还是那些东西
                if (signal(A*pt1[u].x + B*pt1[u].y + C) <= 0)//虽然没写 但这其实仍然是向量积 和原来用法也差不多 如果点在半平面内
                    pt2[hpt2++] = pt1[u];//加入点集2
                else {//否则尝试两次切割
                    if (signal(A*pt1[(u - 1 + hpt1) % hpt1].x + B*pt1[(u - 1 + hpt1) % hpt1].y + C) < 0)
                        pt2[hpt2++] = point_of_intersection(pt1[u], pt1[(u - 1 + hpt1) % hpt1], A, B, C);
                    if (signal(A*pt1[(u + 1) % hpt1].x + B*pt1[(u + 1) % hpt1].y + C) < 0)
                        pt2[hpt2++] = point_of_intersection(pt1[u], pt1[(u + 1) % hpt1], A, B, C);
                }
            }
            for (int p = 0; p < hpt2; p++)
                pt1[p] = pt2[p];
            hpt1 = hpt2;//将点集2赋给点集1
        }
        if (hpt1)//由于剪枝很完善 即便不写hpt1>1或hpt1>2也能正确处理
            printf("Yes\n");//output
        else//如果完全没有可行域
            printf("No\n");//output
    bp1:;//break_point 跳到这里以避开重复输出
    }
}
int main(void)
{
    //freopen("vs_cin.txt", "r", stdin);
    //freopen("vs_cout.txt", "w", stdout);

    while (~scanf("%d", &N))//input
        fun();
}

EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值