算法 {半平面交,多边形的核}

算法 {半平面交,多边形的核}
@LOC: 1

半平面交

定义

二维平面上 有若干条有向直线, 每个有向直线 都取其左侧区域(包括这条线本身), 求这些区域的交集;

@DELI;

#半平面交的形态#
1(不存在): 交集为空;
2(凸多边形): {面积 > 0 >0 >0的凸多边形, 点(由凸多边形塌缩而成), 线段(由凸多边形塌缩而成)};
3(无限区域): {单开口(面积 > 0 >0 >0), 双开口(面积 > 0 >0 >0), 直线(由双开口塌缩而成), 射线(由单开口塌缩而成)};

在这里插入图片描述

性质

令P为[0...i)的半平面交, 则 P ∩ i 边的区域 P \cap i边的区域 Pi边的区域 等于 [0...i]的半平面交;

@DELI;

没有有向边 此时答案为整个平面, 这属于(无限区域/单开口)类型;
一条有向边 此时答案也属于(无限区域/单开口)类型;

@DELI;

答案不会是凹的; 因为 对于凹多边形 一定存在一条边 将这条延长为直线 他会将该多边形分成2个面积为0的区域, 这与有向直线 只保留其左侧区域 相矛盾;

算法

定义

#半平面交的表示# (即算法所求出的答案);
1(不存在): 不需要表示;
2(凸多边形): S : S: S: 所有与答案半平面交有交点的有向直线, L : L: L:对S进行排序后序列 排序要满足L[i]逆时针到L[i+1]的角度 ∈ ( 0 , π ) \in (0, \pi) (0,π);
3(无限区域): S : S: S: 所有与答案半平面交有交点的有向直线, L : L: L:对S进行排序后序列 排序要满足L[i]逆时针到L[i+1]的角度 ∈ ( 0 , π ) \in (0, \pi) (0,π)({双开口和直线}是例外的 他们的范围是 ( 0 , π ] (0, \pi] (0,π]);

@DELI;

排序去重后 边数 > 2 >2 >2, 此时Que表示 所有与 [0, ..., i)的半平面交 有相交的 有向直线 且Que[i]逆时针到Que[i+1]的角度为 ∈ ( 0 , π ) \in (0, \pi) (0,π);
. Que表示的是 一个凸多边形部分轮廓 (单开口 本质上说 也属于凸多边形, 只不过他少了一些边);

性质

MARK: @LOC_0;
算法里用到的unique去重, 这里可以参考LINK: (https://editor.csdn.net/md/?articleId=132162671)-(@LOC_2), 虽然unique( ..., 角度相同) 你可能认为: 对于所有角度相同的有向边 他们被认作是相同的 所以最终只会保留一个, 保留哪一个呢? 是任意一个吗? 不是的!
这里是利用了unique的性质, 在进行去重之前 此时的序列已经满足 :[a1, a2, b1, b2, ..., ]a1,a2角度相同 b1,b2的角度相同, 但是注意 并不是说a1,a2相同的 (他们只是角度相同), 然后此前的排序 还保证了a1一定是在a2左侧, 而unique的性质 他对于一段连续相同的子数组a1,a2 他会保留下来的是a1, 然后把后面的a2给扔掉;

@DELI;

算法得到的是 所有与 半平面交 有交点的有向直线, 注意有交点这个词, 首先 半平面交 他的区域内部 肯定不会有直线 (否则就矛盾了), 因此 这里的有交点 一定是与半平面交的{边/顶点}相交;
举个例子, 半平面交为(0,0) (1,0) (1,1) (0,1)这个正方形, 他所对应的答案 是 ≥ 4 \geq 4 4条有向边! 除了这4条边外, 还可以有(-1,1) -> (0,0)这条有向边 (他与半平面交相交于(0,0)点), 也可以有(-1,10) -> (0,0)这个有向边;

@DELI;

由于算法会对所有有向边进行去重(角度相同的 只会保留1个), 如果你需要原始有向边的信息 可以借助map< __Geometry2D::DirectedLine, vector<int> > Mp 可以记录 完全相同的有向边(角度相同 且重合)的{个数/对应的ID号};
你求出答案后 你获得的是一个DirectedLine X X X, 但他是去重过后的 原始可能有很多个有向边 是等于这个 X X X的, 因此 通过 M p [ X ] Mp[X] Mp[X]就可以 所有重叠的有向边;

@DELI;

排序去重后 且边数 > 2 >2 >2 且此时Que所表示的区域不会是不存在, 当前边 i i i 会与队列尾部Que.back()的边 的角度差 ≥ π \geq \pi π吗?
不会;
#反证#: 说明[Que.back()+1, ..., i-1]这些边 全被淘汰掉了, 会有:Que.back() == i-1, 换句话说 Que.back()i之间 没有边 (证明: {1: 此时Que表示的半平面交 一定是无限区域 不会是凸多边形, 因为Que里的所有边的角度 < π < \pi <π 这不具备构成封闭凸多边形的条件}; {2: 由于1: 此时Que的半平面交 一定包含Que.back()这条有向边的朝着方向的无穷点处, 只有当j完全包含Que区域(Que.back()的无穷远处的点) j才会被淘汰掉 因为j的角度> Que.back(), < Que.back() + Pi 这样的有向直线 其实不会被淘汰掉 因为他一定不会覆盖Que.back()的无穷远处点, 矛盾}; . 因此 此时有Que.back() == i-1, 那么因为边数 > 2 >2 >2 一定存在另外一个边, 排序规则 一定确保了 不会存在相邻两条边的角度差 = π = \pi =π, 矛盾;

@DELI;

1: 两条角度相同的有向直线 保留左侧的有向直线 扔掉右侧的直线;
. (1: 右侧的直线 一定不会是答案, 因为他左侧有 平行线) (2: 从算法代码角度, 否则你从队头/队尾取2个直线 他可能是不存在交点的);

此时 所有有向直线的角度 都是不同的 (这里的角度 是向量的角度 不是直线的角度, 因为涉及到方向 即一个有向直线的角度为 [ 0 , 360 ) [0, 360) [0,360) (向量与x轴正方向的角度), 而直线是[0, 180));

错误

#如果答案为线段, 则有且只有1对有向直线 他俩是平行的#
错误;

       ^
       |
<------->
  |
  v

@DELI;

#判断答案是否为一个点, 如果所有边都共线, 则所有边则是答案#
错误;
假如所有的边 角度是[0,90], 那么 他们的半平面交 不是一个点;

@DELI;

#对有向直线的排序规则 即按照其角度[0, 360)排序, 然后根据其角度是否相同 进行unique; 即sort( lines, 角度递增); unique( lines, 角度是否相同);#

错误; 对于角度相同 我们要保留其最左侧的那个, 因此在sort时 需要保证 对于角度相同的 最靠左的 是排在其他角度相同的 最前面;
. 比如说 角度相同的直线 有a,b,c,d (如果直接排序 他们的次序是任意的), 而最靠左的直线是d(算法是: 角度相同的有向直线x,y 如果x上的任一点z 位于y有向直线的左侧, 即说明x在y的左侧), 此时排序的结果 为d, ..., 然后进行unqiue 就会只保留d 其他的abc就去除了;

代码
template< int _LinesMaximum> class __HalfPlane_Intersection{
public:
    using Item_ = tuple< __Geometry2D::DirectedLine, Tools::Double>;
    vector< Item_> __DirectedLines; // [有向直线, 角度(`[0, 2*Pi)` 起点为`X正方向/X负方向` 取决于`AngleRule`)]
    bool AngleRule;
    //< 他与`get<1>( DirectedLine)`是相关联的; {true: 所有有向直线的角度是以`X正方向`的起点开始计算的)`; false: 从`X负方向`开始`[0, 2*Pi)`};
    //  . 不要调用`get<0>(DirectedLine[?]).Get_angle()`去获取他的角度! 因为他是以*X轴正方向*为起点, 而这里的算法 可能会以*X轴负方向*为起点, 因此要获取其角度 要使用`get<1>( DirectedLine[?])`;
    int Que[ _LinesMaximum], QueHead, QueTail;
    //< `Que[Head...Tail]`为 所有会与*半平面交*有交点的有向直线 按照其`get<1>( DirectedLine[])`(即*AngleRule*)进行*从小到大*排序后的次序;
    //  . `for( int i = QueHead; i <= QueTail; ++i)` 注意`Que[i]`是答案值 而不是`i`;

    __HalfPlane_Intersection(){
        __DirectedLines.reserve( _LinesMaximum);
    }
    void Initialize(){
        __DirectedLines.clear();
    }
    void Add_directedLine( const __Geometry2D::DirectedLine & _directedLine){ // 该有向直线的*左侧区域*为目标区域
    
        __DirectedLines.emplace_back( _directedLine, _directedLine.Get_freeVector().Get_angle());
    }
    void Work(){
#define  QUE_SIZE_  (QueTail - QueHead + 1)
        sort( __DirectedLines.begin(), __DirectedLines.end(), []( const Item_ & _a, const Item_ & _b){
            if( get<1>( _a) != get<1>( _b)) return get<1>( _a) < get<1>( _b);
            return get<0>( _a).Start.Get_relation_withDirectedLine( get<0>( _b)) == -1;
        });
        __DirectedLines.resize( unique( __DirectedLines.begin(), __DirectedLines.end(), []( const Item_ & _a, const Item_ & _b){ return get<1>( _a) == get<1>( _b);}) - __DirectedLines.begin());
        const int N = __DirectedLines.size();
        QueHead = 0, QueTail = -1, AngleRule = true;
        if( N <= 1){
            for( int i = 0; i < N; ++i) Que[ QueTail ++] = i;
        }
        else if( N == 2  &&  get<1>( __DirectedLines[0]) + Tools::Pi == get<1>( __DirectedLines[1])){ // 平行
            int dir = get<0>( __DirectedLines[0]).Start.Get_relation_withDirectedLine( get<0>( __DirectedLines[1]));
            if( dir == 1){ // 无解(将`QUE_SIZE`置为`-1`);
                QueHead = 0, QueTail = -2;
            }
            else{ // 双开口/射线
                Que[ ++ QueTail] = 0, Que[ ++ QueTail] = 1;
            }
        }
        else{
            //>> 重新排序 来使得`[i]到[i+1]`的逆时针角度为`< 180`;
            for( int i = 0; i + 1 < N; ++i){
                if( get<1>( __DirectedLines[i+1]) - get<1>( __DirectedLines[i]) >= Tools::Pi){
                    AngleRule = false;
                    vector< Item_> front( i + 1);
                    for( int j = 0; j <= i; ++j) front[ j] = __DirectedLines[ j];
                    int ind = 0;
                    for( int j = i + 1; j < N; ++j)  __DirectedLines[ ind ++] = { get<0>( __DirectedLines[ j]), get<1>( __DirectedLines[ j]) - Tools::Pi};
                    for( int j = 0; j <= i; ++j)  __DirectedLines[ ind ++] = { get<0>( front[ j]), get<1>( front[ j]) + Tools::Pi};
                }
            }
            for( int i = 0; i < N; ++i){
                while( QUE_SIZE_ >= 2){ // 如果尾部的两个直线的交点 位于当前直线的*右侧* 则删除队尾;
                    const __Geometry2D::DirectedLine & lt = get<0>( __DirectedLines[ Que[ QueTail]]);
                    const __Geometry2D::DirectedLine & ltt = get<0>( __DirectedLines[ Que[ QueTail - 1]]);
                    __Geometry2D::Point p = lt.Get_intersectionPoint_withDirectedLine( ltt);
                    if( p.Get_relation_withDirectedLine( get<0>( __DirectedLines[ i])) == 1) -- QueTail;
                    else break;
                }
                while( QUE_SIZE_ >= 2){ // 如果头部的两个直线的交点 位于当前直线的*右侧* 则删除队头;
                    const __Geometry2D::DirectedLine & lh = get<0>( __DirectedLines[ Que[ QueHead]]);
                    const __Geometry2D::DirectedLine & lhh = get<0>( __DirectedLines[ Que[ QueHead + 1]]);
                    __Geometry2D::Point p = lh.Get_intersectionPoint_withDirectedLine( lhh);
                    if( p.Get_relation_withDirectedLine( get<0>( __DirectedLines[ i])) == 1) ++ QueHead;
                    else break;
                }
                if( QUE_SIZE_ == 1  &&  get<1>( __DirectedLines[i]) - get<1>( __DirectedLines[ Que[ QueTail]]) > Tools::Pi){ // 无解
                    QueHead = 0, QueTail = -2;
                    break;
                }
                //> 当前边`i`不用加入队列 (等价于: [队列的区域为*封闭凸多边形*]&&[队尾和对头的交点 在`i`的左侧])
                if( QUE_SIZE_ >= 2
                        && get<1>( __DirectedLines[ Que[ QueTail]]) > get<1>( __DirectedLines[ Que[ QueHead]]) + Tools::Pi
                        && get<0>( __DirectedLines[ Que[ QueTail]]).Get_intersectionPoint_withDirectedLine( get<0>( __DirectedLines[ Que[ QueHead]]))
                                .Get_relation_withDirectedLine( get<0>( __DirectedLines[ i])) == -1){
                }
                else{
                    Que[ ++ QueTail] = i;
                }
            }
        }

        if( QUE_SIZE_ == -1){ // 无解
            @TODO;
            return;
        }
        //>< `Que[ QueHead...QueTail]`为答案半平面交上的 按照有向边角度的*逆时针*排序 且满足(任意相邻两边的夹角`<180`);
        if( QUE_SIZE_ >= 2
                && get<1>( __DirectedLines[ Que[ QueTail]]) > get<1>( __DirectedLines[ Que[ QueHead]]) + Tools::Pi){
        //< *凸多边形* (其周长为有界值 面积`>=0`) 可以为{点/线段/凸多边形};
            @TODO;
            return;
        }
        //>< *无限区域* (其周长为`+INF` 面积为`0/+INF`) 可以为{射线/直线/双开口无限区域/单开口无限区域}
        ASSERT_(0);
        if( QUE_SIZE_ >= 2){
            const __Geometry2D::DirectedLine & lh = get<0>( __DirectedLines[ Que[ QueHead]]);
            const Tools::Double & ah = get<1>( __DirectedLines[ Que[ QueHead]]);
            const __Geometry2D::DirectedLine & lt = get<0>( __DirectedLines[ Que[ QueTail]]);
            const Tools::Double & at = get<1>( __DirectedLines[ Que[ QueTail]]);
            if( ah + Tools::Pi == at){ // 平行 (方向相反)
                if( QUE_SIZE_ == 2){
                    int dir = lh.Start.Get_relation_withDirectedLine( lt);
                    ASSERT_WEAK_( dir != 1);
                    if( dir == 0){ // *直线* (这两条边重合)
                        @TODO;
                        return;
                    }
                    else if( dir == -1){ // *双开口* (这两条边平行但不重合)
                        @TODO;
                        return;
                    }
                    else{
                        ASSERT_(0); // *无解*, 这种情况已经在上面特判掉了;
                    }
                }
                else{ // *射线*
                    @TODO;
                    return;
                }
            }
        }
        //>< *单开口无限区域* ({0/1/2/...}条边, `Head与Tail的角度差 <= 180`);
        @TODO;
#undef  QUE_SIZE_
    }
};
__HalfPlane_Intersection< ?> HalfPlane_Intersection;

例题

添加辅助边 LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=132491208;

@DELI;

模板 LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=132343915;

多边形的核

定义

二维平面S: (凸/凹)多边形A, ∀ x ∈ S , ∀ y ∈ A , x y 线段内不会与 A 相交    ⟹    x 为 A 的核内的一个点 \forall x \in S, \forall y \in A, xy线段内 不会与A相交 \implies x为A的核内的一个点 xS,yA,xy线段内不会与A相交xA的核内的一个点 ( x ∈ S x\in S xS是平面上任一点, y ∈ A y\in A yA是指 多边形的上的任一点);

#形象定义#: 把A当做是个房子的墙壁, 将一个摄像头放到一个位置x上 如果摄像头可以照射到房子的任意地方(即所有墙壁都被监控) 则该位置x称之为合法的, 所有和合法的位置 称之为 A的核;

#算法定义#: 将多边形A的每条边 按逆时针方向 转换为有向边, 则其半平面交区域 就为 A的核;

性质

#凸多边形的核 为其本身#
因为多边形的核 等于其半平面交, 而凸多边形的半平面交 为其本身;

算法

将多边形A的每条边 按逆时针方向 转换为有向边, 则其半平面交区域 就为 A的核;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值