• 【洛谷4073】[WC2013] 平面图(对偶图)


    题目链接

    • 给定一张 \(n\) 个点和 \(m\) 条边的平面图,每条边有一个边权。
    • \(q\) 次询问,每次给定两个点,询问用一条曲线将它们相连所经最大边权的最小值,要求不能经过外平面。
    • \(5\le n,m,q\le10^5\)

    平面图求对偶图

    这题其实是平面图对偶图相关的一些算法的模板题。(虽然感觉并不会考这种东西)

    首先考虑如何求出平面图的对偶图。

    先把一条无向边视作两条有向边,然后对每个点将它的所有出边极角排序,记下每条出边的下一条是谁。

    任选一条边 \(x\rightarrow y\) 出发,然后选择 \(y\rightarrow x\) 的下一条边 \(y\rightarrow z\) 继续走,以此类推直至走回 \(x\),这样绕出的一个环围出的面就是对偶图中的一个点。

    得出点之后,对每条无向边,在它拆成的两条有向边各自相邻的对偶图中的点之间连边,边权就是这条无向边的边权。这样便建出了对偶图。

    显然求点的过程中每条边只需要访问一次,因此这里的复杂度瓶颈实际上在于极角排序。

    注意到这题中还存在一个限制,就是不能经过外平面。

    发现按照这样走出的环如果围出的是外平面,环上的点将是按照逆时针顺序访问的;如果是内平面,则将是按照顺时针顺序访问的。

    可以利用叉积计算有向面积的方式来判断访问顺序,从而判断围出的面是否为外平面。

    平面图点定位

    为了实现询问,需要判断询问给出的所有点分别在哪个面中,也即属于对偶图中的哪个点。

    可以扫描线,用 set 维护当前存在的所有边的相对顺序。

    那么对于每个点,只要 lower_bound 求出它上方的第一条边,那么它就处于这条边下面的那个面中。

    Kruskal 重构树

    其实也可以 MST+树上倍增。不过我还是选择了直接 Kruskal 重构树。

    询问就是求两点在 Kruskal 重构树中 LCA 的权值。

    代码:\(O(n\log n)\)

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Rg register
    #define RI Rg int
    #define Cn const
    #define CI Cn int&
    #define I inline
    #define W while
    #define N 100000
    #define LG 18
    #define DB double
    #define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
    using namespace std;
    int n,m,Qt,ee=1,lnk[N+5];struct edge {int to,nxt,v,g;}e[2*N+5];
    struct P
    {
    	DB x,y;P(RI a=0,RI b=0):x(a),y(b){}
    	I P operator - (Cn P& o) Cn {return P(x-o.x,y-o.y);}
    	I DB operator ^ (Cn P& o) Cn {return x*o.y-y*o.x;}
    	I friend bool operator < (Cn P& A,Cn P& B) {RI a=A.x>0||!A.x&&A.y<0,b=B.x>0||!B.x&&B.y<0;return a^b?a>b:(A^B)>0;}
    }p[N+5],q[2*N+5],tmp[N+5];
    bool cmp(CI x,CI y) {return tmp[e[x].to]<tmp[e[y].to];}
    int ct,Out,bl[2*N+5];DB s;void dfs(CI x,CI i)//x为起点,当前在第i条边
    {
    	bl[i]=ct;RI j=e[i^1].g;if(bl[j]) return;s+=(p[e[j].to]-p[x])^(p[e[i].to]-p[x]),dfs(x,j);//走反向边的下一条边;s统计有向面积
    }
    int w[N+5];I void Init()//求出对偶图中的点
    {
    	RI i,j,c;for(i=1;i<=n;++i)
    	{
    		for(c=0,j=lnk[i];j;j=e[j].nxt) w[++c]=j,tmp[e[j].to]=p[e[j].to]-p[i];
    		for(sort(w+1,w+c+1,cmp),j=1;j<=c;++j) e[w[j]].g=w[j%c+1];//极角排序,记录每条边的下一条边
    	}
    	for(i=2;i<=ee;++i) !bl[i]&&(++ct,s=0,dfs(e[i^1].to,i),s<=0&&(Out=ct));//每条边只需访问一次;Out记录外平面标号
    }
    namespace K//Kruskal重构树
    {
    	int Nt,V[2*N+5],S[2*N+5][2],fa[2*N+5];I int Fa(CI x) {return fa[x]?fa[x]=Fa(fa[x]):x;}
    	struct edge {int x,y,v;bool operator < (Cn edge& o) Cn {return v<o.v;}}s[N+5];
    	int D[2*N+5],f[2*N+5][LG+1];void dfs(CI x)//倍增预处理
    	{
    		D[x]=D[f[x][0]]+1;for(RI i=1;f[x][i]=f[f[x][i-1]][i-1];++i);x>ct&&(dfs(S[x][0]),dfs(S[x][1]),0);
    	}
    	I void Build()//建树
    	{
    		RI et=0,i;for(i=2;i<=ee;i+=2) bl[i]^Out&&bl[i^1]^Out&&(s[++et]=(edge){bl[i],bl[i^1],e[i].v},0);//存下所有边
    		RI x,y;for(sort(s+1,s+et+1),Nt=ct,i=1;i<=et;++i)//Kruskal
    			(x=Fa(s[i].x))^(y=Fa(s[i].y))&&(V[f[x][0]=f[y][0]=fa[x]=fa[y]=++Nt]=s[i].v,S[Nt][0]=x,S[Nt][1]=y);
    		dfs(Nt);
    	}
    	I int LCA(RI x,RI y)//倍增LCA
    	{
    		RI i;for(D[x]<D[y]&&(swap(x,y),0),i=0;D[x]^D[y];++i) (D[x]^D[y])>>i&1&&(x=f[x][i]);if(x==y) return x;
    		for(i=0;f[x][i]^f[y][i];++i);for(--i;~i;--i) f[x][i]^f[y][i]&&(x=f[x][i],y=f[y][i]);return f[x][0];
    	}
    	I int Q(CI x,CI y) {if(x==Out||y==Out) return -1;RI z=LCA(x,y);return z?V[z]:-1;}//询问
    }
    namespace Scan//扫描线实现点定位
    {
    	DB nw;struct line {int p;DB k,b;bool operator < (Cn line& o) Cn {return k*nw+b<o.k*nw+o.b;}};
    	int c;struct OP {int op;DB x;line s;bool operator < (Cn OP& o) Cn {return fabs(x-o.x)>0.1?x<o.x:op<o.op;}}g[2*N+5];
    	int id[2*N+5];bool cmp(CI x,CI y) {return q[x].x<q[y].x;}
    	int pos[2*N+5];set<line> G;I void Get()
    	{
    		RI i,j;line s;for(i=1;i<=n;++i) for(j=lnk[i];j;j=e[j].nxt) if(p[i].x<p[e[j].to].x)
    		{
    			s.p=bl[j],s.k=(p[e[j].to].y-p[i].y)/(p[e[j].to].x-p[i].x),s.b=p[i].y-s.k*p[i].x;//记录线段解析式
    			g[++c]=(OP){1,p[i].x,s},g[++c]=(OP){-1,p[e[j].to].x,s};//记录开始和结束位置
    		}
    		#define Work() (nw=q[id[j]].x,s=(line){0,0,q[id[j]].y},pos[id[j++]]=(G.empty()||*--G.end()<s)?Out:G.lower_bound(s)->p)//询问点,找到上方第一条线段
    		for(i=1;i<=2*Qt;++i) id[i]=i;for(sort(id+1,id+2*Qt+1,cmp),sort(g+1,g+c+1),i=j=1;i<=c;++i)
    		{
    			W(j<=2*Qt&&q[id[j]].x<g[i].x) Work();//当前操作前的点
    			nw=g[i].x+g[i].op*0.5,~g[i].op?(void)G.insert(g[i].s):(void)G.erase(g[i].s);//当前坐标±0.5防止值相同
    		}
    		W(j<=2*Qt) Work();//没有询问完的点
    	}
    }
    int main()
    {
    	RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%lf%lf",&p[i].x,&p[i].y);
    	RI x,y,z;for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
    	for(scanf("%d",&Qt),i=1;i<=Qt;++i) scanf("%lf%lf%lf%lf",&q[i].x,&q[i].y,&q[Qt+i].x,&q[Qt+i].y);
    	for(Init(),K::Build(),Scan::Get(),i=1;i<=Qt;++i) printf("%d\n",K::Q(Scan::pos[i],Scan::pos[Qt+i]));return 0;
    }
    
  • 相关阅读:
    drfViewSet (指定get关联的函数) 第一波
    drfViewSet之路由简写的方法
    defAPIView 响应 和 接收
    GenericAPIView
    Django接收前端传过来数组的方法
    Django数据迁移失败问题记录 managed = False不知道是不是不迁移的意思有空试试
    Minin混合类
    基于apiview接口实现 查、更新、删除
    drf序列化和反序列化。查询和提交 之Serializer
    python之jsonpath
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4073.html
Copyright © 2020-2023  润新知