题解【[NOI2004]沙丘】

题意简述

  • nnmm 边无向图。 n,mn,m 未知,图的形态未知。你初始在某个点上,带了一个路标,目的是求出 n,mn,m
  • 你可以:查看当前点的状况(相邻点数和是否有路标);在一个点上拿起/放下路标;走到一个相邻节点。
  • 特别注意的是:通过一条边走到相邻节点时,边的编号是临时确定的。即:把进入这个点时经过的边设为 00 号,逆时针依次确定每一条边的编号。
  • n<=100,m<=4000n<=100,m<=4000

分析

​ 思维难度较高的题。

​ 容易想到一种......

​ 。

​ 。

​ 。

​ 然后发现什么方法都想不到。

​ 从最简单的地方开始考虑:我们肯定需要遍历这张图,这样才能确定 nn。如果在遍历每个点时都记下与它相连的边数,我们还能确定 mm。遍历图的方式,常见的有 DFS 和 BFS 。因为我们不知道这张图长什么样,所以采用 DFS 可能会带来一些优势。DFS 树上只有树边和返祖边

​ 然后马上出现了一坨问题:如何确定当前点是以前没遍历到的?如何确定每条边的相对位置?如何确定每个点在其父亲的儿子中的位置?

确定边的相对位置: 假设我们现在走到了 ii ,正准备继续从它开始遍历。我们记 AriAr_i 表示点 ii 把它父结点在它的邻结点中编号为 00 时,它当前的子结点的编号是多少。每次处理完一个子节点并回溯到 ii 准备前往下一个点时,修改 AriAr_i 的值。如果要倒着走,就是 dArid-Ar_iddii 的邻边数)。

​ 这样,我们也可以确定当前子节点在父亲的相对位置了。

​ **确定新点:**假设我们现在走到 ii 的某个邻点 jj ,想确定 jjii 的祖先还是一个新的点。我们先把路标放在 jj 上,然后走到 ii 并不断跳父亲。最后如果跳到根了都没看到路标就说明这是一个新点,回到 jj 记录之并继续遍历,否则说明 jjii 的祖先,我们取走路标再返回 ii

​ 还有一种情况,就是 jj 可能是 ii 的非儿子的后代:如果情况是这样,我们肯定以前遍历过 jj ,也一定通过 jj 走到过 ii 。于是可以记 fli,jfl_{i,j} 表示点 ii 把它父结点在它的邻结点中编号为 00 时,编号为 jj 的结点是否遍历过了。在通过 jj 发现 <i,j><i,j> 返祖边时,我们修改 flifl_i :把路标放在 jj ,回到 ii ,依次看 ii 的每个邻点上是否有路标,找到了之后就取回路标,修改 flfl 并返回 jj 。在回到 ii 时如果发现当前边 flfl11 就跳过之。

为帮助理解过程,手玩一下样例的前半部分:

​ 我们初始在 11 。走到 22 并放下路标,回到 11 发现没遇到路标,于是记录 22 这个新点。回到 22 拿起路标走到 33 ,放下路标,一路走回到 11 都没看到路标,于是记录 33 这个新点。回到 33 拿起路标走到 11 并放下,往回走并发现了路标,说明 1133 的祖先。现在确定 3311 的相对位置,从树上走到 33 放路标,走回到 11 ,在它的邻点转一圈之后发现 11 号边通向了有路标的点。于是记 fl1,1=1fl_{1,1}=1。回到 33 ,发现走完了,回到 22 ,发现也走完了,回到 1111 的下一条边的 flfl11 ,跳过,走再下一条边,到 4......4......

​ 于是我们花了这么长的时间,终于把前三个点做出来了。考虑这样做的复杂度,无非是走每个点的时候都要回到根再回去。所以可能大概也许O(n2+nm)O(n^2+nm) 的。标程实现较劣,很多实现方法都能过。

据说我码风奇异

void init();
void look(int&, bool&);
void put_sign();
void take_sign();
void walk(int);
void report(int, int);
#include<bits/stdc++.h>
using namespace std;
int d,n,m;
int ar[1001],fl[1001][1001],fa[1001],ns[1001];
bool si;
void dfs(int x){
	look(d,si);bool nfl=1;
	for(int i=(x!=1);i<d;i++,look(d,si)){
		if(fl[x][i]){
			if(!nfl)walk(1),walk(0);
			continue;
		}
		ar[x]=i;
		if(nfl)walk(i);
		else walk(1);
		put_sign(),walk(0),m++;
		int ni=x;
		while(!si&&ni!=1)walk(d-ar[ni]),ni=fa[ni],look(d,si);
		if(!si){
			fa[++n]=x,ns[x]=n;
			while(ns[ni])walk((ni==1)?0:ar[ni]),ni=ns[ni];
			take_sign(),dfs(n),nfl=0;
		}else{
			take_sign();int ng=ni;
			while(ni!=x)walk((ni==ng)?0:ar[ni]),ni=ns[ni];
			int nd=0,nj=0,tmp=0;
			put_sign(),walk(0),ni=fa[ni],look(d,si);
			while(ni!=ng)walk(d-ar[ni]),ni=fa[ni],look(d,si);
			bool nsi=0;
			look(nd,nsi);
			for(nj=1;nj<=nd;nj++){
				walk(1),look(tmp,nsi);
				if(nsi)take_sign();
				walk(0);
				if(nsi)break;
			}
			fl[ni][(ar[ni]+nj)%nd]=1,walk(nd-nj),ni=ns[ni],nfl=1;
			while(ni!=x)walk(ar[ni]),ni=ns[ni];
		}
	}
	look(d,si),walk(!nfl),ns[x]=ar[x]=0;
}
int main(){
	init();
	n++;
	dfs(1);
	report(n,m);
	return 0;
}

没错,这题其实只是图上DFS