平面图
在一个平面图中有 (n) 个顶点和 (m) 条直线段,第 (i) 个顶点的坐标为 ((x_i, y_i)),第 (j) 条直线段连接顶点 (u_j) 和顶点 (v_j)。权值为 (h_j),除顶点 (u_j) 和 (v_j) 外直线段 (j) 不经过其他的顶点。任意两条直线段如果存在公共点,则该公共点一定是一个顶点,此时这两条直线段都会连接这个顶点。对于任意的两个顶点 (x) 和 (y),总是可以找到一顶点序列 (a_1, a_2, dots, a_k) 使得 (a_1 = x, a_k = y) 且对于任意 (1 leq i < k) 满足 (a_i) 和 (a_{i + 1}) 被一条直线段直接连接。
这 (m) 条直线段将整个平面分成了若干个区域,其中只有一个区域是无穷大的,其余均是有界的,我们称无穷大的区域为禁区。
现在给出 (q) 次询问,每次给定平面中的任意两个不是顶点且分别不在任意一条直线段上的点 (A) 和 (B),请画一条曲线连接 (A) 和 (B),要求曲线不能经过禁区以及任何顶点,并使得穿过的直线段中权值最大的尽可能小。你需要对每次询问回答这个值最小为多少。
对于全部数据,均满足 (5 leq n, m leq 100000),所有直线段的权值不会超过 (10^9)。所有询问的坐标均为不超过 (10^7) 的非负实数,且保证是 (0.5) 的奇数倍。
基础定义
平面图(planar graph)就是一个可以在平面上画出来的图,并且所有的边只在顶点处相交。平面图的对偶图(dual graph)是将这个平面的每个区域看成点,原图每一条边所属的两个相邻的区域对应在对偶图中的点有连边。
平面图的点定位(point location)就是找出一个点属于这个图的哪个区域。
平面图转对偶图
这个平面图转对偶图的算法似乎被称为最左转线,算法的过程大概是这样的:
-
把所有的边改成双向边。
-
对每个点的出边按照极角排序。
-
找一条没有标记过的边((u,v)),将((u,v))设为当前边。
-
将当前边((u,v))标记。
-
找到(v)的出边中极角序在((v,u))前的最后一条边,设为下一条的当前边。
-
重复这个过程,直到找到一条已经被标记过的当前边,这时候就找到了一个区域。
-
不断重复3,直到所有边都被标记过
我们把逆时针方向围成一个区域的边认为是属于这个区域的,这样一条边就只会属于唯一一个区域。
注意到有一个十分特别的区域,它是无界的,在3过程中,我们计算出这个区域的有向面积,如果是正的,那就说明是有界域,否则就是无界域。
这个算法直观上来讲,就是不断找到一条这条边右边最“左转“的边,这样最后你就会得到一个个由逆时针方向的边构成的区域(当然无界域看起来是顺时针的)。
平面图的点定位
扫描线法可以在(O(nlog n))的复杂度内用于离线点定位。
一些特殊情况还是看miskcoo的博客。
CO int N=1e5+10,inf=2e9;
int n,m,Q;
struct point {float128 x,y;} p[N],q[2*N];
IN float128 angle(CO point&a,CO point&b){
return atan2(b.y-a.y,b.x-a.x);
}
IN float128 calc(CO point&a,CO point&b,float128 x){
return (b.y-a.y)/(b.x-a.x)*(x-a.x)+a.y;
}
struct edge {int u,v,w;} e[2*N],e2[N];
float128 ang[2*N];
namespace Graph{
vector<int> to[N];
int next[2*N],adj[2*N],idx;
IN void circle(int u){
for(int i=u;!adj[i];i=next[i]) adj[i]=idx;
};
void main(){
read(n),read(m);
int s=0;
p[0]={inf,inf};
for(int i=1;i<=n;++i){
scanf("%Lf%Lf",&p[i].x,&p[i].y);
if(p[i].x<p[s].x or (p[i].x==p[s].x and p[i].y<p[s].y)) s=i;
}
for(int i=1;i<=m;++i){
int u=read<int>(),v=read<int>(),w=read<int>();
e[i<<1]={u,v,w},ang[i<<1]=angle(p[u],p[v]),to[u].push_back(i<<1);
e[i<<1|1]={v,u,w},ang[i<<1|1]=angle(p[v],p[u]),to[v].push_back(i<<1|1);
}
for(int i=1;i<=n;++i){
sort(to[i].begin(),to[i].end(),[&](int a,int b)->bool{
return ang[a]<ang[b];
});
for(int j=0;j<(int)to[i].size();++j) // the clockwise next edge
next[to[i][j]^1]=to[i][(j+1)%to[i].size()];
}
++idx,circle(to[s][0]); // the infinite face
for(int i=2;i<=2*m+1;++i)if(!adj[i]) ++idx,circle(i);
for(int i=2;i<=2*m;i+=2){
int u=adj[i],v=adj[i^1];
e2[i>>1]={u,v,u==1 or v==1?inf:e[i].w};
}
}
}
namespace Scanline{
struct cmp{
IN bool operator()(int a,int b){
if(e[a].u==e[b].u) return ang[a]<ang[b];
float128 x=max(p[e[a].u].x,p[e[b].u].x); // compare y[max(x)]
float128 ya=calc(p[e[a].u],p[e[a].v],x);
float128 yb=calc(p[e[b].u],p[e[b].v],x);
return ya<yb;
}
};
set<int,cmp> S;
struct event {int o,i;float128 x;} a[4*N];
int tot,pos[2*N];
void main(){
for(int i=2;i<=2*m+1;++i)
if(p[e[i].u].x<p[e[i].v].x){
a[++tot]={2,i,p[e[i].u].x};
a[++tot]={1,i,p[e[i].v].x};
}
read(Q);
for(int i=1;i<=Q;++i){
scanf("%Lf%Lf",&q[i<<1].x,&q[i<<1].y);
scanf("%Lf%Lf",&q[i<<1|1].x,&q[i<<1|1].y);
a[++tot]={3,i<<1,q[i<<1].x};
a[++tot]={3,i<<1|1,q[i<<1|1].x};
}
sort(a+1,a+tot+1,[&](CO event&a,CO event&b)->bool{
return a.x!=b.x?a.x<b.x:a.o<b.o;
});
int tmp=2*(m+1); // temporary edge
for(int i=1;i<=tot;++i){
if(a[i].o==1) S.erase(a[i].i);
else if(a[i].o==2) S.insert(a[i].i);
else{
p[n+1]=p[n+2]=q[a[i].i],p[n+2].x-=1;
e[tmp]={n+1,n+2,0},ang[tmp]=angle(p[n+1],p[n+2]);
set<int,cmp>::iterator it=S.lower_bound(tmp);
pos[a[i].i]=it==S.end()?1:Graph::adj[*it];
}
}
}
}
namespace Tree{
int fa[N],w[N],siz[N],dep[N];
int find_fa(int u){
return fa[u]==u?u:find_fa(fa[u]);
}
int find_dep(int u){
if(dep[u]) return dep[u];
return dep[u]=fa[u]==u?1:find_dep(fa[u])+1;
}
int route(int u,int v){
int ans=0;
if(dep[u]<dep[v]) swap(u,v);
for(;dep[u]>dep[v];u=fa[u]) ans=max(ans,w[u]);
for(;u!=v;u=fa[u],v=fa[v]) ans=max(ans,max(w[u],w[v]));
return ans;
}
void main(){
sort(e2+1,e2+m+1,[&](CO edge&a,CO edge&b)->bool{
return a.w<b.w;
});
int n=Graph::idx;
for(int i=1;i<=n;++i) fa[i]=i,siz[i]=1;
for(int i=1;i<=m;++i){
int u=find_fa(e2[i].u),v=find_fa(e2[i].v);
if(u==v) continue;
if(siz[u]>siz[v]) swap(u,v);
fa[u]=v,w[u]=e2[i].w,siz[v]+=siz[u];
}
for(int i=1;i<=n;++i) dep[i]=find_dep(i);
for(int i=1;i<=Q;++i){
int u=Scanline::pos[i<<1],v=Scanline::pos[i<<1|1];
int ans=u==1 or v==1?-1:route(u,v); // edit 1: u=v=1
printf("%d
",ans==inf?-1:ans);
}
}
}
int main(){
Graph::main();
Scanline::main();
Tree::main();
return 0;
}
这份代码用的是顺时针找面。