POJ1714 - The Cave

首先,我们需要读懂这个图是什么图。

第一,忽略外面的环,由“任意两点可到达且路径唯一”的条件可知这是一棵树。

第二,因为每个点的度数是三,所以如果只考虑中间的树,除了 \(k\) 以内的点都是叶子,其他的点度数都是三。
考虑什么样的树有很多点度数是三:完全二叉树。但是这个和完全二叉树还有个不同,就是完全二叉树的根度数是二。对于这个问题,我们可以找一个固定的点为根,往它的三个方向延伸出三个节点。每个节点下面都是一个完全二叉树。

第三,再加入外面的环,因为是个平面图,我们就可以给环定序。

image

比如这个图,我们来按照我们的方式重新定义图。\(8\) 是我们钦定的根。从 \(8\) 开始延伸出三个二叉树,分别以 \(1,7,6\) 为根。给外面的环重新定序,\(\{1,3,2,4,5\}\rightarrow \{0,1,2,3,4\}\)

这样,我们给环定了一个方向。我们发现,因为条件给出了图是平面图,所以每棵二叉树的叶子节点必然是环上的一个连续区间。因为环和序列不一样,我们不能直接给两个区间排序,也就不能通过偏序方式定左右儿子。

但是我们发现,如果左儿子和右儿子都已经定好顺序了,那么左儿子的最右叶子一定是右儿子的最左叶子在环上的前驱。因为一共有三棵二叉树,所以不可能两边都相接。那么就可以唯一的确定当前的儿子是左儿子还是右儿子。

image

以这个图为例,我们来继续思考路径的特点。

因为是一条回路,所以其实外环上的点是等价的,我们也不需要考虑哪个点是 \(1\)。但是 \(n\) 是我们人为给中间的树定的根,对它的讨论是至关重要的。

我们考虑经过根 \(a\) 的路径和外环最近的两个交点。

image

image

我们发现,如果这条路径把图分成了两部分,它就没法同时经过两半。回路就不能成立。所以经过 \(n\) 的路径必须保证所有的点都在它的另一边。

image
image
image

尝试发现,只有这三个方案符合条件。而细想即发现,我们一定只能沿着二叉树的最外侧前进,否则这棵树本身就会出现两部分。而另一条路径一定不在此二叉树里。

所以,合法的路径只有三种,就是沿着三棵二叉树相邻的边缘画一个弧。

下面我们以第三种(棕色)方案举例。

image

我们发现,棕色路径把原图分割成了若干个二叉树,分别是没有被路径涉及的那棵完整二叉树和棕色路径劈树分裂出来的子树。这些二叉树按照环的顺序排列,互相之间只在外环上相邻。

而我们的问题就是给这张图找到回路。

我们观察单一的一个二叉树。我们发现,因为它和外界没有其他位置的交集,所以必须从一侧进,另一侧出(回路没有方向,所以我们钦定一开始环的方向为进入出去的顺序)。

那么,既然不同二叉树之间的连接方式确定,我们就需要确定二叉树内部的顺序。

我们发现,对于一棵二叉树,其入口和出口确定,其遍历方式也确定

image

\(right\)\(left\) 代表整个左子树和右子树。

右边进去,左边出来,考虑从右子树如何到左子树。如果从下面的边走,就永远不可能到达根,因为右子树所有点被遍历过之后,根节点度数就是 \(1\) 了,从左子树不可能走到它。因此,我们必须是右子树右进上出,左子树上进左出以经过根节点。

image

右边进去,上面出来。考虑如何从右子树到左子树。如果从右子树经过根到达左子树,就不可能再回溯到上面去。所以一定是右子树右进左出,左子树右进上出,到达根往上。

image

上面进来,左边出去,进入左子树之前一定要进入右子树,所以右子树上进左出,左子树右进左出。

我们发现,这三种情况互相调用,只要用这三个函数递归直到一个节点,往那边出就是往哪边连边。也就可以唯一确定二叉树的访问顺序。

也就是,我们只要枚举一开始的三种路径选哪种,就可以唯一的确定路径。

我们需要实现的步骤如下:

  1. 给环定序。

  2. 给每个二叉树定左右儿子和最左最右

  3. 枚举分割路径来自哪两棵二叉树

  4. 跑一遍分割路径,把原树划分成若干个二叉树

  5. 对每个二叉树递归计算内部的结果

  6. 找到三种方案中经过 \(1\) 边最少的,输出答案。


int n,k;
const int N=1000000;
int id[N+5],id_cnt;
int cost[N+5],inv[N+5];
int vis[N+5];
vt<pii>vv[N+5];
vt<int>vs[N+5];
inline void dfs_outer(int x,int p){
	inv[id_cnt]=x,id[x]=id_cnt++,vis[x]=1;
	rd(o,(int)vv[x].size()){
		pii i=vv[x][o];
		if(i.first<=k){
			if(i.first!=p&&!vis[i.first]){
				dfs_outer(i.first,x);
			}
			if(i.first!=p&&x!=1){
				cost[id[x]]=i.second;
			}
		}
	}
}
int ls[N+5],rs[N+5];
int lp[N+5],rp[N+5];
int fa[N+5],fp[N+5];
int L[N+5],R[N+5];
inline void dfs_inner(int x,int p){
	if(x<=k){
		ls[x]=rs[x]=0;
		L[x]=R[x]=id[x];
		return;
	}
	rd(o,(int)vv[x].size()){
		pii i=vv[x][o];
		if(i.first!=p){
			dfs_inner(i.first,x);
			if(!ls[x]){
				ls[x]=i.first,lp[x]=i.second;
			}else rs[x]=i.first,rp[x]=i.second;
		}
	}
	if((R[rs[x]]+1)%k==L[ls[x]]){
		swap(ls[x],rs[x]);
		swap(lp[x],rp[x]);
	}L[x]=L[ls[x]],R[x]=R[rs[x]];
	fa[ls[x]]=fa[rs[x]]=x;
	fp[ls[x]]=lp[x];
	fp[rs[x]]=rp[x];
}
int ans=1e9,res;
vt<pii> vans,vres;
inline void btree_RL(int x);
inline void btree_RU(int x);
inline void btree_UL(int x);
inline int pre(int x){
	return inv[(id[x]-1+k)%k];
}
inline void btree_RL(int x){
	if(x<=k){
		res+=cost[id[x]];
		vres.pb(pii(x,pre(x)));
		return;
	}
	btree_RU(rs[x]);
	res+=lp[x];
	vres.pb(pii(x,ls[x]));
	btree_UL(ls[x]);
}
inline void btree_RU(int x){
	if(ls[x]){
		btree_RL(rs[x]);
		btree_RU(ls[x]);
	}
	res+=fp[x];
	vres.pb(pii(x,fa[x]));
}
inline void btree_UL(int x){
	if(x<=k){
		res+=cost[id[x]];
		vres.pb(pii(x,pre(x)));
		return;
	}res+=rp[x];
	vres.pb(pii(x,rs[x]));
	btree_UL(rs[x]);
	btree_RL(ls[x]);	
}
inline void btree_solve(int yl,int yr){
	int xl=vv[n][yl].first,xr=vv[n][yr].first,lt=3-yl-yr;
	res=0;vres.clear();
	res+=cost[R[xl]],res+=fp[xr];
	vres.pb(pii(xr,n));
	vres.pb(pii(inv[R[xl]],pre(inv[R[xl]])));
	for(int i=inv[R[xl]];i!=n;i=fa[i]){
		res+=fp[i];vres.pb(pii(i,fa[i]));
		if(ls[i]){
			btree_RL(ls[i]);
		}
	}
	btree_RL(vv[n][lt].first);
	for(int i=xr;ls[i];i=ls[i]){
		res+=lp[i];vres.pb(pii(i,ls[i]));
		btree_RL(rs[i]);
	}
	if(res<ans){
		ans=res;
		vans=vres;
	}
}
inline void input(){
	cin>>n>>k;
	rep(i,1,3*n/2){
		int a,b,c;
		cin>>a>>b>>c;
		vv[a].pb(pii(b,c));
		vv[b].pb(pii(a,c));
	}
}
inline void output(int x){
	cout<<x;vis[x]=1;
	rd(o,(int)vs[x].size()){
		int j=vs[x][o];
		if(!vis[j]){
			cout<<" ";
			output(j);
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	input();
	dfs_outer(1,0);
	rep(i,1,k){
		rd(o,vv[i].size()){
			pii j=vv[i][o];
			if(j.first<=k&&id[j.first]==(id[i]+1)%k){
				cost[id[j.first]]=j.second;
			}
		}
	}
	rd(o,(int)vv[n].size()){
		pii i=vv[n][o];
		dfs_inner(i.first,n);
		fa[i.first]=n;
		fp[i.first]=i.second;
	}
	if(L[vv[n][2].first]==(R[vv[n][0].first]+1)%k){
		swap(vv[n][1],vv[n][2]);
	}
	rd(i,3)btree_solve(i,(i+1)%3);
	rd(o,(int)vans.size()){
		pii i=vans[o];
		vs[i.first].pb(i.second);
		vs[i.second].pb(i.first);
	}
	rep(i,1,n)vis[i]=0;
	output(1);
	cout<<endl;
	return 0;
}
//Nyn-siyn-hert

代码比较长,但是步骤条理清晰,不是非常难写。

posted @ 2023-05-30 22:52  jucason_xu  阅读(8)  评论(0编辑  收藏  举报