P6976 [NEERC2015]Distance on Triangulation

tag:点分治,对偶图

思路

考虑分治解决问题,每次选一个三角形,处理经过这个三角形的询问,再递归下去。那么我们要做的就是使剩下部分尽量平均。

将原图的对偶图画出来,通俗来讲,就是把一个三角形当成一个点,再把有公共边的三角形连起来,会发现是一棵树(不考虑最外面的那个面),于是发现这个过程就是点分治的过程。每次处理跨分治中心的询问,并递归解决其它询问

复杂度\(O((n+q)logn)\)

建图

最关键的一步就是把对偶图弄出来。考虑用拓扑,每次将度数为 \(2\) 的点拿出来,那么这个点和它相连的两个点就构成了一个三角形,然后删掉这个点和这 \(2\) 条边。一直处理下去就可以找出所有三角形。

for(register int i=1; i<=n; i++) if(d[i]==2) q[++r] = i;
while(l<=n-2){   //一共只有n-2个三角形
    int x = q[l++]; vis[x] = true;
    int a=-1, b=-1;
    for(register int i=0, tp=to[x].size(); i<tp; i++) 
    	if(!vis[to[x][i]]) 
        	if(a==-1) a = to[x][i]; 
        	else b = to[x][i];
    t[++cnt] = (tri){x,a,b};
    d[a]--; d[b]--;
    if(d[a]==2) q[++r] = a; 
    if(d[b]==2) q[++r] = b;
}
    

建树的话,考虑每次处理到 \(x\) 的时候,当前三角形为 \((x,a,b)\),那么当前三角形与父亲三角形相连的那条边一定是 \((a,b)\),就把这个三角形记在边 \((a,b)\) 上(我比较懒直接用 \(map\))。然后处理到 \((x,a,b)\) 时看一看每条边是否接了一个三角形。

if(mp[make(x,a)]) T.Add_Edge(cnt,mp[make(x,a)]);
if(mp[make(x,b)]) T.Add_Edge(cnt,mp[make(x,b)]);
if(mp[make(a,b)]) T.Add_Edge(cnt,mp[make(a,b)]);
else mp[make(a,b)] = cnt;

分治

分治过程和点分治大同小异,这里主要讲一个直接求出最短路的方法。(当然你也可以直接bfs三遍,不过常数有点大)

先手动赋值根结点对应的三个点的\(dis\)数组。

假设 \(dfs\) 到了 \(x\),那么 \(x\) 对应的三角形 \((A,B,C)\) 一定有两个顶点是之前处理过的,所以只需要找出没有处理的那个顶点( 设为 \(C\) ),然后设 \(dis_C=min\{dis_A,dis_B\}+1\) 即可。

/*
bel[x]表示x属于哪个子树
::bel[x] 的意思就是 bel[x],只不过这里和函数内部的bel重名了,所以要用::去调用
*/
if(!::bel[t[x].a]) np = t[x].a;
if(!::bel[t[x].b]) np = t[x].b;
if(!::bel[t[x].c]) np = t[x].c;
::bel[np] = bel; p[++top] = np;
for(register int i=0; i<3; i++) 
    dis[np][i] = inf,
    dis[np][i] = min(min(dis[t[x].a][i],dis[t[x].b][i]),dis[t[x].c][i])+1;

杂项

这道题写对拍造数据可能不太好造,提供一个思路:用双向链表把 \(1\) ~ \(n\) 串起来,每次随机一个还在的点 \(x\),把 \(x\) 左右两个点 \(l\)\(r\) 连起来(即输出 \(l\ r\)),然后删掉 \(x\)

完整代码

posted @ 2021-06-26 14:09  oisdoaiu  阅读(56)  评论(0编辑  收藏  举报