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\)。