信息学奥赛一本通:骑马修栅栏,一“修”到底!

Fencing!

题目点我哦:点击打开链接

哈哈哈虽然“fencing”是击剑的意思

简单的来说就是欧拉路,裸的,AC了(一定要看下面的详解啊):

#include<bits/stdc++.h>
using namespace std;
const int N=10000+5,M=10000000+5;
int g[N][N],path[M],de[M],start,a,b,n,len,maxx=-10000000,minn=10000000;
void init(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a>>b;
		g[a][b]++;g[b][a]++;
		de[a]++;de[b]++;
		maxx=max(maxx,max(a,b));
		minn=min(minn,min(a,b));
	}
	start=minn;
	for(int i=minn;i<=maxx;i++)
	    if(de[i]%2){
	        start=i;
	        break;
	    }
}
void wans(int x){
	int i;
	for(i=minn;i<=maxx;i++){
		if(g[x][i]){
			g[x][i]--;g[i][x]--;
			wans(i);
		}
	}
	path[++len]=x;
}
void print(){
	for(int i=len;i>=1;i--)
	    cout<<path[i]<<endl;
}
int main(){
	init();
	wans(start);
	print();
	return 0;
}

这道题其实本身并不是很难,只是在存储的方式上有争议:

在“wans”这个函数中有一个地方会有两种写法:
 
 1、先走边,后存储答案:
void wans(int x){
	int i;
	for(i=minn;i<=maxx;i++){
		if(g[x][i]){
			g[x][i]--;g[i][x]--;
			wans(i);
		}
	}
	path[++len]=x;
}
 2、先存储答案,后走边:
void wans(int x){
	int i;
	path[++len]=x;
	for(i=minn;i<=maxx;i++){
		if(g[x][i]){
			g[x][i]--;g[i][x]--;
			wans(i);
		}
	}
}
很明显,两种之中有一种是错误的!

按照题意,我们要尽量的按照点的编号小的节点先输出的方式来遍历整个图,那么我们会不会有这样一种情况捏?
情况一:假设从某一个编号较小的奇点出发,并在某一个岔路口会遇到一个编号相较其他相邻节点编号要小的节点,这时根据编号较小点先被枚举的规律,程序会直接走入“死胡同”,并且深陷其中!

情况二:将4节点编号改成了8!



很明显,我们将那个岔路口并且具有“死胡同”的边的初始节点编号改大了一些,比其他的边所连接的点的编号都要大,那么这时,在岔路口就不会先往死胡同走( 3 -> 8 ),而是直接( 3 -> 5 ),避免了死胡同先存储的情况,所以当且仅当度数大于2的岔路口处且相连接的节点中编号最小的节点不在“死胡同里”两种存法都是相同的!

当然,度数为奇数的终点节点在“死胡同”里的话,只有后存储,也就是第1种存储方法是可取的。

为什么呢?

举个形象的例子:

在CDQZ-GX 439寝室有6个人,某一天他们因为王者和吃鸡还有洛谷的运势问题产生了纠纷!他们决定大打出手,干翻那些单杀了他们的人、或者在荒野行动中用载具误伤他们的队友、以及洛谷运势大吉的人,而且从来不手下留情,非死即伤,可谓“三年血赚,死刑不亏”!

他们这时候如果脑子充满了怒火——像一群交配失败的狒狒的话,他们就会一见到人就打,不分青红皂白,就连自己没有仇恨的人,他们都会打!最后WRY将TPY打残了,才发现原来自己要打的不是TPY啊,……这就很尴尬了,但是有没有什么可以弥补的了,就像你先访问了在死胡同里的节点,但是当发现自己还有外面的栅栏没有修完的时候,为时已晚!

但是他们要是脑子清醒,打人之前先问一下——

WRY:“你的洛谷运势是什么啊?你单杀了谁啊?你是不是吃鸡的时候用载具碾死我的那个狗b啊?”
SHT:“我今日大吉……”

话音未落,WRY飞起一拳,正中SHT裆下,当然是打对了!于是WRY继续复仇计划——

WRY:“你呢?TPY?”
TPY:“唉,我今天大凶!而且和你一起被WSH碾死了!还被LC单杀了……时运不济,命途多舛啊!”
WRY:“同是天涯沦落人,相逢何必曾相识啊!走!干翻他们!”

于是避免了一场灾祸……

和这些原理一样,“后存储”(第一种)方法也会进入死胡同, 但是它不会进入死胡同就马上存储,而是一直往下“询问是否含有仇恨”(是否有尽头)。如果找到了尽头,只会有两种情况:

 1、已经找完全部点
这时由于每走过一条边都会删除这条边和这条边的反向边,所以到最会这个节点(尽头)的时候一定没有其他边了,退栈!
(执行wans(dfs)的栈)
2、未找完全部节点
这是头脑最不清晰的地方:由于某种机缘巧合——终点只可能是尽头,所以从尽头开始往回走到岔路口这一段也就是最后
修的那一部分,顺序一模一样!到达尽头是和方法2相同的退栈过程,但每次退栈时都会存储一个正确答案的逆序数列。直
到退到了岔路口,程序便会去寻找新的边,所以会照样正常的寻找。但是注意,因为“死胡同”里的所有边都在“误走”
的过程中全部删完了,所以它不会重复的再搜一遍“死胡同”,而是会走到尽头时自动退栈,逆序存储!
上面是走完死胡同后的图。
接着:
 
 
最后的模样!
数组 path[ i ]会长这样:
{ 0 4 3 7 6 5 3 2 1 0 < repeat 10005 times > };

ZYL同学问会不会出现多次岔路口?不会的,因为在欧拉路(一笔画路)中奇点个数有且仅有2个,所以“死胡同”只有两个
,一条是始点所在的路,一条是终点所在的路!

解决了!碎觉!啊,肝要炸了……

寒假开始了!训练第一天,开心!不过wans好像不会回来了呢…………

继续努力吧……
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值