POJ3310-Caterpillar 树的直径+两次dfs+自个儿琢磨的小等式!

题源:http://poj.org/problem?id=3310
这道题折腾了我好久。WA了九次,不断修改判断方法。终于在今天早上A了。第一次独立的想出来一个别人想不出来的小思维,还是挺开心的~
〇、先说题目大意
给你一个树(但默认不是树,默认给的是图,你得判断是不是树,不能直接拿树去做)的点数n 和边数m,然后给你m对边,让你判断是不是个毛毛虫。那么什么是毛毛虫呢?官方一点的描述就是,找出树最长的一条(就是树的直径),然后这条直径所连接的其他点必须都直接连在这棵树上!(类比一下高中学的有机,就是说一个烷烃分子,它必须是正烷,不能连接甲基乙基丙基balabala,否则所连的甲基乙基上面的氢原子就无法直接与主链相连了,这道题要求就是每个氢都与主链相连)。想象一下毛毛虫的样子,一条躯体,而它的毛都是直接长在身体上的对吧!知道这些之后,再看一下根据所给的点和边的关系,如何判断是不是毛毛虫。
一、再分享一下这道题的心路历程。
卡题之后搜了题解发现方法有很多,并查集、dfs、图的遍历什么的。拿到这道题,第一感觉是树的直径,然后两遍dfs求出直径来了(求树的直径可以用dp或者dfs/bfs,时间复杂度都是O(n),可以在csdn搜一下模板)。求出直径,然后就没思路了。然后我分析了几个毛毛虫的特点。发现了边和度数的关系(写在了下面的解题思路里),如果满足这个关系,就是毛毛虫!事实证明自己琢磨的这个关系是对的,但是刚开始总是WA WA WA。然后就搜题解,发现我WA的原因是默认它是棵树(谁让样例给的都是树呢。。。)。然后看了题解知道要判断一下给的图是不是一棵树,才能继续。判断图是不是树的方法很简单,首先判断点和边的关系,不满足m=n-1 直接输出不是毛毛虫,然后换下一组样例。若满足,就判断这个图是不是一个无回路的连通图!注意两个要点:无回路、连通。判断方法有很多。一是并查集:不断合并所提供的两个点,如果合并时出现无法合并(两个点之前已经被合并)的情况,那么就说明有回路,就不行!二是搜索:用一个数组标记所有搜索到的点,然后再遍历每个点看标记情况,如果存在没被标记过的,那么说明不连通(搜不到这个点啊~)。而因为我们是在m=n-1,且没有重边(这里题目没说重边的事,我按默认没有重边的方法提交可以a,所以题目应该是默认没有重边的)的情况下进行的并查集和搜索,所以说:不连通一定有环,有环一定不连通。因为边的数量就是那么多,你的边如果不去干正事(用来连通没被连通的点),肯定不干正事(自成回路)去了!所以说并查集和搜索这两种方法,虽然一个判断环,一个判断是否连通,但是在m=n-1且没重边的基础上,两者是等价的!
二、大体的解题思路~
终于说到要点了呜呜呜。
1、先判断m==n-1是否成立,不成立直接不行。
2、然后在第1条成立的基础上,从任意一个点进行搜索(并查集也可),本次搜索有两个作用:1、看是否为一棵树(无回路且连通),不成立直接不行。2、记录下直径的一个端点f,下次用这个f作为搜索起点。
(插播 下一条之前 先上我发现的经验证可行的结论:如果这棵树是毛毛虫,那么树的直径上所有点的度数之和 deg 减去树的直径 ans 一定等于边数m 。证明:这本质上就是一个通过度数求边数的方法。因为 如果这棵树是毛毛虫,那所有边都会和直径上的点直接相连,统计每个点的度数之和,自然会统计上所有的边。相反若不是毛毛虫,因为只统计了直径上的点的度数和,所以有边会被遗漏。而直径上的边因为都连着直径上的两个点,所以会被重复计算两次,所以需要减去一份总直径。)
3、在第1、2条成立的基础上,进行判断。注意,下面是重点:从上次搜索得到的f出发,只搜没标记过 且度数不为1的点(不搜毛毛虫的毛),搜到头。此间需要记录两个数据:1、从起点开始走过的所有点的度数之和,2、走过的路的长度(因为不搜度数为1的点,所以得到的路的长度其实是 树的直径减去1)。两者相减若等于边数,那证明是毛毛虫。
你可能会问:第二次搜索的时候,避开毛毛虫的毛(度数为1的顶点)就可以了吗?是的。因为如果是毛毛虫,不搜它的毛,那只有这一条路可以走。相反,若你搜到了第二条路,那每多一条支路,算出的边数就会比真正的边数多1!(因为多一条支路时相当于直径上有个三度顶点,但是最后只减去了一份直径,所以少减了1,故边数多1)。换言之,只要满足毛毛虫,那么一定只有一条路,度数、边数一定满足(树的直径上所有点的度数之和 deg 减去树的直径 ans 等于边数m)这个公式。否则就不是毛毛虫!
哎呀累死了,再说说坑吧
三、需要注意的坑!
1、默认不是个树,需要用并查集或者搜索判断。我刚开始默认是树来做 一直WAWAWA!!!
2、不用考虑重边。
下面上AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<stack>
#include<algorithm>
using namespace std;
int n,m,cnt,ans,f,deg;//cnt是边总数(双向边所以一条变两条) ans是求出的直径总长度  deg是直径上点的总度数 
struct node{
	int x,y,next;
}side[302];
int first[102],in[102],con[102];
int add(int x,int y){//邻接表模板
	cnt++;
	side[cnt].x=x;
	side[cnt].y=y;
	side[cnt].next=first[x];
	first[x]=cnt;
	return 0;
}
int init(){//初始化
	cnt=0,deg=0,ans=0;//这三个变量每组只用一次 所以只在最开始初始化为0即可
	memset(first,-1,sizeof(first));
	memset(side,0,sizeof(side));
	memset(con,0,sizeof(con));
	memset(in,0,sizeof(in));
	return 0;
}
int dfs1(int x,int len,int ans){//len是当前长度 ans是遍历过的最长长度! 
	con[x]=1;//搜了这个点就标记上	//为什么这里相比dfs2又重新定义了个ans呢 
	if(ans<len){				//因为第一次搜索得到的长度不需要记录 
		ans=len;				//所以为了把全局变量的ans写入init(); 
		f=x;					//就只让ans在每组测试样例开始随着init为0就好 
	}
	for(int i=first[x];i!=-1;i=side[i].next){
		if(con[side[i].y]==0){//若当前确定的边的终点来过 就停止遍历
			dfs1(side[i].y,len+1,ans);
		}
	}
	return 0;
}
int dfs2(int x,int len){
	con[x]=1;
	deg+=in[x];//统计总度数
	if(ans<len){
		ans=len;
	}
	for(int i=first[x];i!=-1;i=side[i].next){
		if(con[side[i].y]==0){
			if(in[side[i].y]!=1){//只搜度数不为1的点 这样会少了一个长度和一个度数 
				dfs2(side[i].y,len+1);
			}
			else{
				con[side[i].y]=1;
			}
		}
	}
	return 0;
}
int main(){
	int T=1;
	while(scanf("%d",&n)!=EOF){
		if(n==0) break; 
		scanf("%d",&m);
		init();
		int a,b;
		for(int i=1;i<=m;i++){
			scanf("%d%d",&a,&b);
			in[a]++,in[b]++;
			add(a,b);
			add(b,a);
		}
		if(m!=n-1){
			printf("Graph %d is not a caterpillar.\n",T++);
			continue;
		}
		dfs1(1,0,0);//先搜一次 两个功能!可以得到最远的f 和所有点的con
		int flagg=1;//flagg判断是否能访问到每个点 来判断是否连通
		for(int i=1;i<=n;i++){//这里能判断有没有环 (见分析
			if(con[i]==0){//没有访问到该点 则不连通
				flagg=0;
				break;
			}
		}
		if(flagg==0){
			printf("Graph %d is not a caterpillar.\n",T++);
			continue;
		}
		memset(con,0,sizeof(con));//只把标记取消就好了 
		dfs2(f,0);//搜第二次  计算度数直径边数的关系
		//因为避开了一度点来求直径  所以最后一个一度点不会被统计上  所以结果恰好少了一个一度顶点和一条边的长度 
		deg++,ans++;//补充少遍历的一个点和一个边 不加的话对结果应该也没有影响 
		//printf("\n%d %d %d\n",deg,ans,m);
		if(deg-ans==m){//核心判断依据
			printf("Graph %d is a caterpillar.\n",T++);
		}else{
			printf("Graph %d is not a caterpillar.\n",T++);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值