P3884 [JLOI2009]二叉树问题——蒟蒻的暴力解法

因为蒟蒻太蒟蒻,所以代码臭又长~~~~~~

算法:DFS、Floyd

前言

最近在刷数据结构,刷到了二叉树的题单,二叉树存储遍历访问等等操作仍是不熟。昨天晚上刷到一题,思路明确,但是因为数据给出的顺序问题,没有满分,所以今天重构代码。思想算法和昨天一样,仅仅是调整了数据输入的顺序,AC了本题。


一、题目

题目描述

如下图所示的一棵二叉树的深度、宽度及结点间距离分别为:

  • 深度:4
  • 宽度:4
  • 结点 8 和 6 之间的距离:8
  • 结点 7 和 6 之间的距离:3

其中宽度表示二叉树上同一层最多的结点个数,节点 u, v之间的距离表示从 u 到 v 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数。

给定一颗以 1 号结点为根的二叉树,请求出其深度、宽度和两个指定节点 x, y之间的距离。

输入格式

 

输出格式

输入三行,每行一个整数,依次表示二叉树的深度、宽度和 x, yx,y 之间的距离。

输入输出样例

输入 #1

10                                
1 2                            
1 3                            
2 4
2 5
3 6
3 7
5 8
5 9
6 10
8 6

输出 #1

4
4
8

说明/提示

 

二、题目分析

1. 边权(距离)?

节点 u, v之间的距离表示从 u 到 v 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数。

什么两倍什么边数,都弄晕了QAQ

其实就是父亲到儿子距离为1,儿子到父亲距离为2....... 

2. 基本思路

(1)深度?

遍历树节点,儿子深度=父亲深度+1,并用ans_depth记录最深的深度即可。

(2)宽度?

数组width[n]记录深度为n的节点数,设树节点深度为i,则探索该节点时width[i]++。遍历整棵树的节点即可。用ans_width记录某一深度最多的节点数即可。

(3)给定x,y节点的最短距离?

emmm,求图中任意两点的最小距离?Floyd算法即可~

3. 初步代码

根据以上三个问题的解决算法,可以得到以下的源代码。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<limits.h>
#include<stack>
#include<cstdlib>
#include<queue>
#include<map>
#include<vector>

#define INF 0x3f3f3f3f
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

using namespace std;

int dis[105][105];
int n;
int depth[105],width[105];
 
int main()
{
	/************初始化阶段***************/
	cin>>n;
	n-=1; 
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			dis[i][j]=INF;
			dis[j][i]=INF;
			if(i==j)
			{
				dis[i][j]=0;
			}
		}
	}
	memset(depth,0,sizeof(depth));
	memset(width,0,sizeof(width));
	 
	/**************读入数据**************/
	depth[1]=1;
	width[1]=1;
	int ans_depth=1;
	int ans_width=1;
	int x,y;
	for(int i=1;i<=n;++i)
	{
		cin>>x>>y;
		depth[y]=1+depth[x];
		ans_depth=ans_depth>depth[y]?ans_depth:depth[y];
		width[depth[y]]++; 
		ans_width=ans_width>width[depth[y]]?ans_width:width[depth[y]];
		dis[x][y]=1;
		dis[y][x]=2;
	} 
	/*********Floyd求任意两点最短路径*****/
	for(int k=1;k<=n;++k)
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				if(dis[i][k]+dis[k][j]<dis[i][j])
				{
					dis[i][j]=dis[i][k]+dis[k][j];
				}
	/*************答案输出部分***********/
	cin>>x>>y;
	cout<<ans_depth<<endl<<ans_width<<endl<<dis[x][y];
 	return 0;
}
//默认是按照从上往下从左往右的顺序读入所以只有73分,需要存树然后dfs 

是不是思路清晰呢?然而没有AC,仅得到了73分。

这是为什么呢?

三、 代码改进

1. 代码错误分析

随然没有通过全部测试点,但也并没有全部WA,可见基本算法思路还是正确的。问题在哪呢?

上述代码,默认为读入的数据符合以下规则

①读入u,v则u是父亲节点,v是儿子节点(然而题目只说明两个点相连

②读入的顺序是从整棵树从上往下,从左往右(事实上,如果只是给出两个节点相连,可以给出任意满足整体相连规则的顺序)

也就是说,如果只是顺序地看输入的u,v,那么这棵树的模样,还是不能轻易构造出来的!

2. 代码修改思路

既然是这颗树建的不明白不清楚,那么我就老老实实把前期工作做好——把树建好。

最初的切入点:

(1)建树? 

既然给的u,v顺序可以是打乱的,那我就先把u,v连接的这些信息一条条记录下来。

然后从根节点1出发,一步步找到孩子节点,再找孩子结点的孩子节点...... 

/******************************/
    
struct tp1{
	int from,to;
};
tp1 data[105];

bool dt[105];

struct TreeNode{
	int lchild,rchild;
	bool flag;
};
TreeNode node[105];

/******************************/

    nd.push(1);//根节点是1 

    while(nd.size()!=0)
	{
		int fa=nd.front();
		for(int i=1;i<n;++i)
		{
			if(!dt[i])//如果该组数据没用过 
			{
				int child=-1;
				if(data[i].from==fa)
				{
					child=data[i].to;
				}
				if(data[i].to==fa)
				{
					child=data[i].from;
				}
				//看看谁是父亲 
				if(child!=-1)
				{
					dt[i]=true;//该组数据使用过了 
					if(node[fa].flag==true)//如果存在左子树,就添加右子树 
					{
						node[fa].rchild=child;	
					}
					else//否则添加左子树 
					{
						node[fa].lchild=child;
						node[fa].flag=true;	
					}
					nd.push(child);
				}
			}
		}
		nd.pop();//该结点已经寻找完儿子节点了 
	}
/******************************/

(2) Floyd的距离矩阵?

树都已经建立好了,从根节点1开始,深搜遍历,就好啦~~(我封装成了一个函数)

void makeTree(int n)
{
	int lchild=node[n].lchild,rchild=node[n].rchild;
	if(lchild==INF&&rchild==INF)return;
	if(lchild!=INF)
	{
		dis[n][lchild]=1;
		dis[lchild][n]=2;
		makeTree(lchild);
	}
	if(rchild!=INF)
	{
		dis[n][rchild]=1;
		dis[rchild][n]=2;
		makeTree(rchild);
	}
}

(3)ans_width与ans_depth? 

照之前所说的,遍历一边答案就出来了!同样还是用DFS(同样封装成一个函数)

void dfs(int n,int d)
{
	depth[d]++;
	ans_depth=ans_depth>d?ans_depth:d;
	ans_width=ans_width>depth[d]?ans_width:depth[d];
	
	int lchild=node[n].lchild,rchild=node[n].rchild;
	if(lchild==INF&&rchild==INF)return;
	if(lchild!=INF)
	{
		dfs(lchild,d+1);
	}
	if(rchild!=INF)
	{
		dfs(rchild,d+1);
	}
}

3. 代码2.0 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<limits.h>
#include<stack>
#include<cstdlib>
#include<queue>
#include<map>
#include<vector>

#define INF 0x3f3f3f3f
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

using namespace std;

struct tp1{
	int from,to;
};
tp1 data[105];
bool dt[105];

struct TreeNode{
	int lchild,rchild;
	bool flag;
};
TreeNode node[105];

queue<int> nd;

int n;

void initialize(void);//data,num,node,dis

int dis[105][105];

void makeTree(int);

int ans_depth,ans_width;
void dfs(int,int);

int depth[8];

int main()
{	
	IOS;
	cin>>n;
	initialize();
	for(int i=1;i<n;++i)
	{
		cin>>data[i].from>>data[i].to;
	}
	nd.push(1);//根节点是1 
	
	while(nd.size()!=0)
	{
		int fa=nd.front();
		for(int i=1;i<n;++i)
		{
			if(!dt[i])//如果该组数据没用过 
			{
				int child=-1;
				if(data[i].from==fa)
				{
					child=data[i].to;
				}
				if(data[i].to==fa)
				{
					child=data[i].from;
				}
				//看看谁是父亲 
				if(child!=-1)
				{
					dt[i]=true;//该组数据使用过了 
					if(node[fa].flag==true)//如果存在左子树,就添加给右子树 
					{
						node[fa].rchild=child;	
					}
					else//否则添加左子树 
					{
						node[fa].lchild=child;
						node[fa].flag=true;	
					}
					nd.push(child);
				}
			}
		}
		nd.pop();//头结点已经寻找完子节点了 
	}
	makeTree(1);
	for(int k=1;k<=n;++k)
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				if(dis[i][k]+dis[k][j]<dis[i][j])
				{
					dis[i][j]=dis[i][k]+dis[k][j];
				}
	int x,y;
	cin>>x>>y;
	dfs(1,1);
	cout<<ans_depth<<endl<<ans_width<<endl<<dis[x][y];
	return 0;
}

void makeTree(int n)
{
	int lchild=node[n].lchild,rchild=node[n].rchild;
	if(lchild==INF&&rchild==INF)return;
	if(lchild!=INF)
	{
		dis[n][lchild]=1;
		dis[lchild][n]=2;
		makeTree(lchild);
	}
	if(rchild!=INF)
	{
		dis[n][rchild]=1;
		dis[rchild][n]=2;
		makeTree(rchild);
	}
}


void initialize()//dt,node,dis
{
	memset(dt,false,sizeof(dt));
	memset(depth,0,sizeof(depth));
	for(int i=1;i<=n;++i)
	{
		node[i].flag=false;
		node[i].lchild=INF;
		node[i].rchild=INF;
		for(int j=1;j<=n;++j)
		{
			dis[i][j]=INF;
		}
	}
}

void dfs(int n,int d)
{
	depth[d]++;
	ans_depth=ans_depth>d?ans_depth:d;
	ans_width=ans_width>depth[d]?ans_width:depth[d];
	
	int lchild=node[n].lchild,rchild=node[n].rchild;
	if(lchild==INF&&rchild==INF)return;
	if(lchild!=INF)
	{
		dfs(lchild,d+1);
	}
	if(rchild!=INF)
	{
		dfs(rchild,d+1);
	}
	
}

 总结

最初最直接的思路还是非常容易想到的,遇到的问题也非常直接——树的建立难题。千辛万苦将树建好,再运用蒟蒻的算法思路,就能成功AC本题啦!

这是蒟蒻的想法和冗杂操作,看到解法区还有什么ACL等等大犇算法,心生艳羡。

虽不能至,心向往之。与诸君共勉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值