算法 {半平面交,多边形的核}
@LOC: 1
半平面交
定义
二维平面上 有若干条有向直线, 每个有向直线 都取其左侧区域(包括这条线本身), 求这些区域的交集;
@DELI;
#半平面交的形态#
1(不存在): 交集为空;
2(凸多边形): {面积
>
0
>0
>0的凸多边形, 点(由凸多边形塌缩而成), 线段(由凸多边形塌缩而成)};
3(无限区域): {单开口(面积
>
0
>0
>0), 双开口(面积
>
0
>0
>0), 直线(由双开口塌缩而成), 射线(由单开口塌缩而成)};
性质
令P为[0...i)
的半平面交, 则
P
∩
i
边的区域
P \cap i边的区域
P∩i边的区域 等于 [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.youkuaiyun.com/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.youkuaiyun.com/md/?not_checkout=1&articleId=132491208
;
@DELI;
模板 LINK: https://editor.youkuaiyun.com/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的核内的一个点 ∀x∈S,∀y∈A,xy线段内不会与A相交⟹x为A的核内的一个点 ( x ∈ S x\in S x∈S是平面上任一点, y ∈ A y\in A y∈A是指 多边形的边上的任一点);
#形象定义#: 把A当做是个房子的墙壁, 将一个摄像头放到一个位置x上 如果摄像头可以照射到房子的任意地方(即所有墙壁都被监控) 则该位置x称之为合法的, 所有和合法的位置 称之为 A的核;
#算法定义#: 将多边形A的每条边 按逆时针方向 转换为有向边, 则其半平面交区域 就为 A的核;
性质
#凸多边形的核 为其本身#
因为多边形的核 等于其半平面交, 而凸多边形的半平面交 为其本身;
算法
将多边形A的每条边 按逆时针方向 转换为有向边, 则其半平面交区域 就为 A的核;