2019 HDU 多校 6631 line symmetric---计算几何

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6631

题意:给定一个简单多边形,问是否可以通过移动至多一点使得该图形变成一个轴对称的简单多边形,对应的点要求对称。

题解:首先对称轴一定是多边形上的点p[i], p[i+1]或者p[i], p[i+2]上的中垂线,然后我们就可以暴力的枚举每条对称轴,判断是否可以通过移动一个点使得该图形成为一个轴对称图形。需要注意的是改变后的轴对称图形也要是简单多边形,所以要排除点重复和边相交的情况:(大致如下)

如果移动c点与d点对称,则点重复,如果移动d点和d点对称,则AD与BC相交。(当然上图只是描述这个意思,实际上4个点一定可以移成等腰梯形之类的轴对称图形)为了解决上诉情况,可以把点分成两部分,即相对于对称轴而言D和A同在对称轴的一侧,且C和B同在对称轴的一侧(不包含在对称轴上),如果不能通过位置关系把点分成两部分,则该条对称轴就不符合条件。

代码:

#include<bits/stdc++.h>
#define db double
#define pb push_back
#define mem(a, b) memset(a, b, sizeof (a))
#define debug(x) cout << x << endl
using namespace std;
const db eps = 1e-10;
const db pi = acos(-1);
int sign(db x){ if(fabs(x) < eps) return 0;  return x > 0 ? 1 : -1; }
int cmp(db k1, db k2){ return sign(k1-k2); }
int inmid(db k1, db k2, db k3){ return sign(k2-k1)*sign(k3-k1) <= 0; }//k1在k2、k3之间
struct point{
    db x, y;
    point(){}
    point(db k1, db k2){ x = k1, y = k2; }
    point operator + (const point &k1) const { return point(x+k1.x, y+k1.y); }//向量加法、点+向量=点
    point operator - (const point &k1) const { return point(x-k1.x, y-k1.y); }//向量减法、点-点=向量
    point operator * (db k1) const { return (point){x*k1, y*k1}; }//向量数乘
    point operator / (db k1) const { return (point){x/k1, y/k1}; }//向量数除
    int operator == (const point &k1) const { return cmp(x,k1.x)==0 && cmp(y,k1.y)==0; }//比较两个点(向量)是否相同
    point turn(db k1){return (point){x*cos(k1)-y*sin(k1), x*sin(k1)+y*cos(k1)};}//逆时针旋转
    point turn90(){return (point){-y, x};}//逆时针旋转90度
    bool operator < (const point k1) const{
        int a = cmp(x, k1.x);
        if(a == -1) return 1; else if(a == 1) return 0; else return cmp(y,k1.y)==-1;
    }//比较两个点(向量)的大小,x越小则点越小;若x相等,则y越小点越小。可以实现按点的坐标排序
    db len(){ return sqrt(x*x+y*y); }//向量模长
    db len2(){ return x*x+y*y; }//向量模长的平方
    point unit(){ db k = len(); return point(x/k, y/k); }
    db angle() { return atan2(y, x); }
    point getdel(){if (sign(x)==-1||(sign(x)==0&&sign(y)==-1)) return (*this)*(-1); else return (*this);}
    //当横坐标为负时,或横坐标为0纵坐标为负时,将点按原点做对称??? 角度是[-π/2, π/2]
    int getp() const { return sign(y)==1 || (sign(y)==0&&sign(x)==-1); }
    //判断点在12象限,或者在x的负半轴上??? 角度是(0, π]
    void scan(){ scanf("%lf%lf", &x, &y); };
    void print(){ printf("%.11f %.11f\n", x, y); }
};
int inmid(point k1, point k2, point k3){ return inmid(k1.x,k2.x,k3.x) && inmid(k1.y,k2.y,k3.y); }//k3 在 [k1,k2] 内
db dis2(point k1, point k2){ return (k1.x-k2.x)*(k1.x-k2.x) + (k1.y-k2.y)*(k1.y-k2.y); }
db dis(point k1, point k2){ return sqrt(dis2(k1, k2)); }
db cross(point k1, point k2){ return k1.x*k2.y - k1.y*k2.x; }//叉乘
db dot(point k1, point k2){ return k1.x*k2.x + k1.y*k2.y; }//点乘
db rad(point k1, point k2){ return atan2(cross(k1,k2), dot(k1,k2)); }//向量夹角
int compareangle (point k1,point k2){//极角排序,[-π, π]
    return k1.getp()<k2.getp() || (k1.getp()==k2.getp() && sign(cross(k1,k2))>0);
}
point proj(point q, point k1, point k2){ point k=k2-k1; return k1+k*(dot(q-k1,k)/k.len2()); }// q 到直线 k1,k2 的投影
point reflect(point q, point k1, point k2){ return proj(q,k1,k2)*2-q; }// q 关于直线 k1, k2 的对称点
int clockwise(point k1,point k2,point k3){ return sign(cross(k2-k1,k3-k1)); }// k1 k2 k3 逆时针 1 顺时针 -1 否则 0
int checkll(point k1,point k2,point k3,point k4){// 直线相交判定 叉积计算面积,两直线不平行必相交(除去重合的情况),平行时,三角形面积相等
    return cmp(cross(k3-k1,k4-k1),cross(k3-k2,k4-k2))!=0;
}
point getll(point k1,point k2,point k3,point k4){// 直线交点
    db w1=cross(k1-k3,k4-k3),w2=cross(k4-k3,k2-k3); return (k1*w2+k2*w1)/(w1+w2);
}
int intersect(db l1,db r1,db l2,db r2){// 区间相交判定,区间1在区间2前
    if(l1>r1) swap(l1,r1); if(l2>r2) swap(l2,r2); return cmp(r1,l2)!=-1 && cmp(r2,l1)!=-1;
}
db displ(point q, point k1, point k2){// 点到直线的距离
    return fabs(cross(k2-k1, q-k1)) / (k2-k1).len();
}
struct line{// p[0]->p[1]
    point p[2];
    line(){}
    line(point k1,point k2){p[0]=k1; p[1]=k2;}
    point& operator [] (int k){return p[k];}
    int include(point k){ return sign(cross(p[1]-p[0],k-p[0]))>0; }//点在直线左侧的判定,不包括在直线上
    point dir(){return p[1]-p[0];}//方向向量
    line push(){ // 向外 ( 左 ) 平移 eps
        point delta=(p[1]-p[0]).turn90().unit()*eps;
        return {p[0]-delta, p[1]-delta};
    }
};
int checkll(line k1,line k2){ return checkll(k1[0],k1[1],k2[0],k2[1]); }//直线相交判定
point getll(line k1,line k2){ return getll(k1[0],k1[1],k2[0],k2[1]); }//直线交点
int parallel(line k1,line k2){ return sign(cross(k1.dir(),k2.dir()))==0; }//直线平行判定,可以重合

vector<point>poly;
int n;

point midpoint(point k1, point k2){//求中点
    return point((k1.x+k2.x)/2, (k1.y+k2.y)/2);
}

line getaxis(point k1, point k2){//求k1k2的中垂线
    point a1 = midpoint(k1, k2);
    point a2 = a1 + (k1-k2).turn90();
    return line(a1, a2);
}

bool online(point k1, line l){//点在直线上的判定
    return sign(cross(l[1]-l[0], l[1]-k1)) == 0;
}

bool symmetry(point k1, point k2, line l){//k1k2关于l对称的判定
    return sign(dot(k2-k1, l[1]-l[0])) == 0 && online(midpoint(k1, k2), l);
}

bool check(int x, int y){
    line axis = getaxis(poly[x], poly[y]);
    vector<point>pl, pr;
    int change = 0, l, r;
    l = x, r = y;
    pl.pb(poly[x]);
    pr.pb(poly[y]);
    while(true){//把多边形上的点分成两部分
        if(l == r){
            if(!online(poly[l], axis)) change++;
            break;
        }
        if(l != x && r != y) {
            pl.pb(poly[l]);
            pr.pb(poly[r]);
        }
        if((l-1+n)%n == r) break;
        l = (l-1+n)%n, r = (r+1)%n;
    }
    l = (x+1)%n, r = (y-1+n)%n;
    if(l == r && !online(poly[l], axis)) change++;//当check的点是i和i+2时,需要检查i+1这个点是否在对称轴上
    int cnt = pl.size();
    for(int i = 0; i < cnt; i++){//两部分中对应的点不能交叉
        if(sign(cross(axis[1]-axis[0], pl[0]-axis[0])) != sign(cross(axis[1]-axis[0], pl[i]-axis[0])) &&
           sign(cross(axis[1]-axis[0], pr[0]-axis[0])) != sign(cross(axis[1]-axis[0], pr[i]-axis[0])) ) return false;
    }
    for(int i = 0; i < cnt; i++){//判断需要移动的点的个数
        if(!symmetry(pl[i], pr[i], axis)){
            if(online(pl[i], axis) && online(pr[i], axis)) return false;
            change++;
        }
    }
    return change <= 1;
}

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        poly.clear();
        scanf("%d", &n);
        point tmp;
        for(int i = 0; i < n; i++){
            tmp.scan();
            poly.pb(tmp);
        }
        if(n <= 4) puts("Y");
        else{
            bool flag = false;
            for(int i = 0; i < n && !flag; i++){
                if(check(i, (i+1)%n)){
                    flag = true;
                }
                if(check(i, (i+2)%n)){
                    flag = true;
                }
            }
            if(flag) puts("Y");
            else puts("N");
        }
    }
    return 0;
}
/* 测试样例
100
5
0 0
0 2
2 3
2 -1
1 1
6
0 0
4 0
4 1
1 7
2 4
0 1
5
0 0
2 -1
1 5
-1 5
0 2
5
0 0
2 -1
1 5
0 2
-2 -1
5
0 0
1 1
3 1
4 0
3 -2
6
-3 3
-5 -5
-1 -3
1 -3
5 -5
3 3
7
-3 3
-5 -5
-1 -3
0 2
5 -5
3 3
0 4
6
0 0
1 1
0 1
0 2
2 2
2 0
6
0 0
2 0
2 2
0 2
0 1
1 1
6
0 0
2 1
1 3
3 2
3  0
1 -1
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值