PAT_A1021题解:The Deepest Root

题目

PTA | 程序设计类实验辅助教学平台 (pintia.cn)

本人做法

思路

利用DFS来实现对整个图进行遍历,如果得到的连通分支数>1,则该图与树不等价;

并在DFS的过程中保存能达到的最大深度,最后进行排序以获得相应节点。

代码

#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

#define MAXV 10005

vector<int> adjList[MAXV];
bool visited[MAXV] = {false};
int depth[MAXV];
int N,K;

int DFS(int n,int depth){
    visited[n] = true;
    int ans = depth;
    for(int i = 0;i < adjList[n].size();i++){
        if(!visited[adjList[n][i]]){
            ans = max(DFS(adjList[n][i],depth+1),ans);
        }
    }
    return ans;
}

void DFS_Trans(){
    for(int i = 1;i <= N;i++){
        if(!visited[i]){
            DFS(i,1);
            K++;
        }
    }
}

int main(){
    cin>>N;
    int m,n;
    if(N==1){
        cout<<"1"<<endl;
        return 0;
    }
    for(int i = 1;i <= N;i++){
        cin>>m>>n;
        adjList[m].push_back(n);
        adjList[n].push_back(m);
    }
    memset(visited,false,sizeof(visited));
    DFS_Trans();
    if(K>1) {
        cout<<"Error: "<<K<<" components";
        return 0;
    }
    memset(visited,false,sizeof(visited));
    for(int i = 1;i <= N;i++){
        depth[i] = DFS(i,1);
        memset(visited,false,sizeof(visited));
    }
    int maxv = 0;
    for(int i = 1;i <= N;i++){
        if(depth[i]>maxv) maxv = depth[i];
    }
    for(int i = 1;i <= N;i++){
        if(depth[i]==maxv) cout<<i<<endl;
    }
}

但该写法的复杂度较高,可能是数据比较弱吧,能ac掉。一旦数据比较强,写法就会G掉。

考虑好边界,特况特判。

所以还是建议学习下面的更优解法。

更优做法

思路

1.判断图是否等价为树

连通且边数为N-1的图必为一棵树。

而题目中的输入边数已经确定为N-1条,所以可以用并查集,根据最后集合个数是否为1来判断给定数据是否可使图连通。

2.如何找到满足要求的节点

先选择任意一个节点,从该节点来遍历整棵树,获取能达到的最深顶点的集合(记为A),然后从集合A中任意一个节点出发遍历整棵树,获取能达到的最深顶点的集合(记为B)。这样A∪B则是题目中要求的使树高最大的根节点。

证明

从直观上来看,该解法是正确的。但为保证严谨性,还需要进行数学上的证明。

1. 证明:从任意结点出发遍历,得到的根节点一定是所求根结点的一部分

使用反证法。

直径:把最大树高对应的路径拉成一条直线,称为树的直径。

设R为使得树高最大的根节点,那么存在某个叶子结点L,使得R到L的长度即为树的最大数高。

把R到L的路径拉成一条直线(即为该树的直径)。

(图中以线段的长度代表结点间距)

现从任意一个结点X开始遍历,假设遍历得到的最深节点是Y,且Y≠L≠R,那么有OY>OL,所以有OR+OY>OR+OL。那么Y才是由R为根节点的最大树高的对应叶子结点,而这与前提R到L的距离是最大树高矛盾,因此假设不成立。

所以:从任意结点X进行树的遍历,得到的最深结点一定是R或者L,即所求根节点的一部分。

2. 证明:所有直径一定有一段公共重合区域(或是交于一个公共点)

(1)先证明任意两条直径一定相交

假设存在两条长度相同的不相交直径XY,WZ(字母表示结点)

根据树的连通性,一定存在点P,Q是互相可达的,其中P在XY上,Q在WZ上。若如此,则会存在一条更长的直径XZ,WY,这与直径的定义相矛盾,因此假设不成立。

即,任意两条直径一定相交。

(2)再证明所有直径一定有一段公共重合区间或一个公共点

假设三条直径X1Y1,X2Y2,X3Y3相交且不交于同一点,如图所示。则可通过P、Q、R拼接出更长存的直径,这与直径的定义矛盾,因此假设不成立。

有公共重合区域的情况同理可得。

3.证明:所有两次遍历结果的并集为所求的根结点的集合

现在看有公共区域的那个图,如果所选结点在公共区域PQ之间,那么最深节点会是A1~Am或者B1~Bk中的其中一组或是全部。而在第二次遍历时,任取一个根节点即可遍历完整另外一侧所有最深的结点。

同理,如果第一次遍历的结点在P的左侧(或Q的右侧),则最深节点一定是B1~Bk(或是A1~Am),这样在第二次遍历时,任取一个根节点即可遍历完整另外一侧所有的最深结点。

代码

用vector

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 100005;
vector<int> G[N];

bool isRoot[N];
int father[N];

int findFather(int x) {
	if (x == father[x])
		return x;
	int z = findFather(father[x]);
	father[x] = z;
	return z;
}

void Union(int a, int b) {
	int faA = findFather(a);
	int faB = findFather(b);
	if (faA != faB) {
		father[faA] = faB;
	}
}

void init(int n) {
	for (int i = 1; i <= n; i++) {
		father[i] = i;
	}
}

int calBlock(int n) {
	int block = 0;
	for (int i = 1; i <= n; i++) {
		isRoot[findFather(i)] = true;
	}
	for (int i = 1; i <= n; i++) {
		block += isRoot[i];
	}
	return block;
}

int maxHeight = 0;
vector<int> temp, ans;

void DFS(int u, int h, int pre) {
	if (h > maxHeight) {
		temp.clear();
		temp.push_back(u);
		maxHeight = h;
	} else if (maxHeight == h) {
		temp.push_back(u);
	}
	for (int i = 0; i < G[u].size(); i++) {
		if (G[u][i] == pre)
			continue;
		DFS(G[u][i], h + 1, u);
	}
}

int main() {
	int a, b, n;
	scanf("%d", &n);
	init(n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &a, &b);
		G[a].push_back(b);
		G[b].push_back(a);
		Union(a, b);
	}

	int block = calBlock(n);
	if (block != 1) {
		printf("Error: %d components\n", block);
	} else {
		DFS(1, 1, -1);//从1号结点开始遍历
		ans = temp;//此时temp为集合A
		DFS(ans[0], 1, -1);
		for (int i = 0; i < temp.size(); i++) {
			ans.push_back(temp[i]);//此时的temp为集合B
		}
		sort(ans.begin(), ans.end());
		printf("%d\n", ans[0]);
		for (int i = 1; i < ans.size(); i++) {
			if (ans[i] != ans[i - 1])
				printf("%d\n", ans[i]);
		}
	}
	return 0;
}

 提醒自己注意的点:

1.并查集一定要记得初始化

2.DFS的两种变式:

(1)如何记录DFS过程中达到的深度

(2)无向图中保存前一个结点保证跳过回去的边

用set

用set容器的话可以避免掉去重的操作。

#include <cstdio>
#include <cstring>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;

const int N = 100005;
vector<int> G[N];

bool isRoot[N];
int father[N];

int findFather(int x) {
	if (x == father[x])
		return x;
	int z = findFather(father[x]);
	father[x] = z;
	return z;
}

void Union(int a, int b) {
	int faA = findFather(a);
	int faB = findFather(b);
	if (faA != faB) {
		father[faA] = faB;
	}
}

void init(int n) {
	for (int i = 1; i <= n; i++) {
		father[i] = i;
	}
}

int calBlock(int n) {
	int block = 0;
	for (int i = 1; i <= n; i++) {
		isRoot[findFather(i)] = true;
	}
	for (int i = 1; i <= n; i++) {
		block += isRoot[i];
	}
	return block;
}

int maxHeight = 0;
set<int> temp,ans;

void DFS(int u, int h, int pre) {
	if (h > maxHeight) {
		temp.clear();
        temp.insert(u);
		maxHeight = h;
	} else if (maxHeight == h) {
		temp.insert(u);
	}
	for (int i = 0; i < G[u].size(); i++) {
		if (G[u][i] == pre)
			continue;
		DFS(G[u][i], h + 1, u);
	}
}

int main() {
	int a, b, n;
	scanf("%d", &n);
	init(n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &a, &b);
		G[a].push_back(b);
		G[b].push_back(a);
		Union(a, b);
	}

	int block = calBlock(n);
	if (block != 1) {
		printf("Error: %d components\n", block);
	} else {
		DFS(1, 1, -1);
		ans = temp;
		DFS(*ans.begin(), 1, -1);
		for (set<int>::iterator it = temp.begin();it!=temp.end();it++) {
			ans.insert(*it);
		}
        for (set<int>::iterator it = ans.begin();it!=ans.end();it++) {
			printf("%d\n", *it);
		}
	}
	return 0;
}

 注意:set正好是默认升序的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值