通过四道编程题总结图的搜索(DFS/BFS)——PTA(1013/1021/1034/1076)

7 篇文章 0 订阅
7 篇文章 0 订阅

1013 Battle Over Cities (25 分)

It is vitally important to have all the cities connected by highways in a war. If a city is occupied by the enemy, all the highways from/toward that city are closed. We must know immediately if we need to repair any other highways to keep the rest of the cities connected. Given the map of cities which have all the remaining highways marked, you are supposed to tell the number of highways need to be repaired, quickly.
For example, if we have 3 cities and 2 highways connecting city 1 -city
2 and city 1 -city 3. Then if city 1is occupied by the enemy, we must have 1 highway repaired, that is the highway city 2 -city 3 .

Input Specification:

Each input file contains one test case. Each case starts with a line containing 3 numbers N (<1000), M and K, which are the total number of cities, the number of remaining highways, and the number of cities to be checked, respectively. Then M lines follow, each describes a highway by 2 integers, which are the numbers of the cities the highway connects. The cities are numbered from 1 to N. Finally there is a line containing K numbers, which represent the cities we concern.

Output Specification:

For each of the K cities, output in a line the number of highways need to be repaired if that city is lost.

Sample Input:

3 2 3
1 2
1 3
1 2 3

Sample Output:

1
0
0

思路

求除去给出点及点所连通路之后,剩下图中的连通分支个数,连成通路只需要连通分支个数-1.
邻接矩阵:

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int maxx=1005;
int vec[maxx][maxx];
vector<bool> pan;
void dfs(int x,int a){
	if(x==a) return ;
	for(int i=1;i<=n;i++){
		if(vec[x][i]&&!pan[i]){
			pan[i]=true;
			dfs(i,a);
		}
	}
}
int main()
{
	cin>>n>>m>>k;
	int a,b;
	for(int i=1;i<=m;i++){
		cin>>a>>b;
		vec[a][b]=1;
		vec[b][a]=1;
	}
	while(k--){
		pan.clear();
		pan.resize(maxx,false);
		cin>>a;
		int num=0;
		for(int i=1;i<=n;i++){
			if(i==a) continue;
			if(!pan[i]){
				pan[i]=true;
				dfs(i,a);	
				num++;
			}
		}
		cout<<num-1<<endl;
	}
	return 0;
}

邻接表:

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int maxx=1005;
vector<int> vec[maxx];
vector<bool> isVisit;
void dfs(int x,int a){
	if(x==a) return ;
	for(int i=0;i<vec[x].size();i++){
		int node=vec[x][i];
		if(!isVisit[node]){
			isVisit[node]=true;
			dfs(node,a);
		}
	}
}
int main()
{
	cin>>n>>m>>k;
	int a,b;
	while(m--){
		cin>>a>>b;
		vec[a].push_back(b);
		vec[b].push_back(a);
	}
	while(k--){
		cin>>a;
		int num=0;
		isVisit.clear();
		isVisit.resize(maxx,false);
		for(int i=1;i<=n;i++){
			if(i==a) continue;
			if(!isVisit[i]){
				dfs(i,a);
				num++;
			}
		}
		cout<<num-1<<endl;
	}
	return 0;
}

1021 Deepest Root (25 分)

A graph which is connected and acyclic can be considered a tree. The height of the tree depends on the selected root. Now you are supposed to find the root that results in a highest tree. Such a root is called the deepest root.

Input Specification:

Each input file contains one test case. For each case, the first line contains a positive integer N (≤10 4) which is the number of nodes, and hence the nodes are numbered from 1 to N. Then N−1 lines follow, each describes an edge by given the two adjacent nodes’ numbers.

Output Specification:

For each test case, print each of the deepest roots in a line. If such a root is not unique, print them in increasing order of their numbers. In case that the given graph is not a tree, print Error: K components where K is the number of connected components in the graph.

Sample Input 1:

5
1 2
1 3
1 4
2 5

Sample Output 1:

3
4
5

Sample Input 2:

5
1 3
1 4
2 5
3 4

Sample Output 2:

Error: 2 components

思路

首先利用并查集检查是否为一棵树or若干个连通分量,接着从任意顶点开始dfs(为满足测试用例,默认从最小序号1开始)找到最深的顶点集合,再从这个集合选择任意顶点(为满足测试用例,默认从首个顶点开始)再次dfs,两次dfs得到的并集即为所求。
DFS+并查集:

#include<bits/stdc++.h>
using namespace std;
const int maxx=10005;
vector<int> a(maxx),temp,ans;
vector<int> vec[maxx];
int n;
void init(int n){
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
}
int find(int x){
	return x==a[x]? x: a[x]=find(a[x]);
}
void unite(int x,int y){
	x=find(x);
	y=find(y);
	if(x==y) return ;
	a[x]=y;
}
bool isSame(int x,int y){
	return find(x)==find(y);
}
int pan(){
	map<int,int> mp;
	for(int i=1;i<=n;i++){
		mp[find(i)]++;
	}
	return mp.size();
}
int maxh=0;
vector<bool> isVisit(maxx,false);
void dfs(int x,int h){
	isVisit[x]=true;
	if(h>maxh){
		temp.clear();
		maxh=h;
		temp.push_back(x);
	} else if(h==maxh){
		temp.push_back(x);
	}
	for(int i=0;i<vec[x].size();i++){
		int node=vec[x][i];
		if(!isVisit[node]){
			dfs(node,h+1);
		}
	}
}
int main()
{
	cin>>n;
	int x,y;
	init(n);
	for(int i=0;i<n-1;i++){
		cin>>x>>y;
		vec[x].push_back(y);
		vec[y].push_back(x);
		unite(x,y);
	}	
	int num=pan();
	if(num>1){
		printf("Error: %d components",num);
		return 0;
	}
	dfs(1,0);
	ans=temp;
	isVisit.clear();
	isVisit.resize(maxx,false);
	dfs(ans[0],0);
	ans.insert(ans.end(),temp.begin(),temp.end());
	set<int> se(ans.begin(),ans.end());
	for(auto it=se.begin();it!=se.end();it++){
		cout<<*it<<endl;
	}
	return 0;
}

实际上,dfs本身就可以用来计算连通分量个数,所以可以不用并查集而直接利用bfs判断。

#include<bits/stdc++.h>
using namespace std;
const int maxx=10005;
vector<int> a(maxx),temp,ans;
vector<int> vec[maxx];
int n;
int maxh=0;
vector<bool> isVisit(maxx,false);
void dfs(int x,int h){
	isVisit[x]=true;
	if(h>maxh){
		temp.clear();
		maxh=h;
		temp.push_back(x);
	} else if(h==maxh){
		temp.push_back(x);
	}
	for(int i=0;i<vec[x].size();i++){
		int node=vec[x][i];
		if(!isVisit[node]){
			dfs(node,h+1);
		}
	}
}
int main()
{
	cin>>n;
	int x,y;
	for(int i=0;i<n-1;i++){
		cin>>x>>y;
		vec[x].push_back(y);
		vec[y].push_back(x);
	}	
	int num=0;
	for(int i=1;i<=n;i++){
		if(!isVisit[i]){
			dfs(i,0);
			num++;
		}	
	}
	if(num>1){
		printf("Error: %d components",num);
		return 0;
	}
	ans=temp;
	isVisit.clear();
	isVisit.resize(maxx,false);
	dfs(ans[0],0);
	ans.insert(ans.end(),temp.begin(),temp.end());
	set<int> se(ans.begin(),ans.end());
	for(auto it=se.begin();it!=se.end();it++){
		cout<<*it<<endl;
	}
	return 0;
}

1034 Head of a Gang (30 分)

One way that the police finds the head of a gang is to check people’s phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls made between the two persons. A “Gang” is a cluster of more than 2 persons who are related to each other with total relation weight being greater than a given threshold K. In each gang, the one with maximum total weight is the head. Now given a list of phone calls, you are supposed to find the gangs and the heads.

Input Specification:
Each input file contains one test case. For each case, the first line contains two positive numbers N and K (both less than or equal to 1000), the number of phone calls and the weight threthold, respectively. Then N lines follow, each in the following format:

Name1 Name2 Time
where Name1 and Name2 are the names of people at the two ends of the call, and Time is the length of the call. A name is a string of three capital letters chosen from A-Z. A time length is a positive integer which is no more than 1000 minutes.

Output Specification:
For each test case, first print in a line the total number of gangs. Then for each gang, print in a line the name of the head and the total number of the members. It is guaranteed that the head is unique for each gang. The output must be sorted according to the alphabetical order of the names of the heads.

Sample Input 1:
8 59
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
Sample Output 1:
2
AAA 3
GGG 3
Sample Input 2:
8 70
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
Sample Output 2:
0

思路

要求统计每一个结点数大于3且总边权大于k的连通分支,并输出每个满足要求的连通分支中点权最大的结点以及所含结点的个数。
首先这种字符串表示点的问题我们要利用哈希map来对字符串和整数进行一一对应,最终用整数代替结点解决问题:一个exchange()处理函数返回每个字符串独有的结点下标;
审清题意:点权=总入度+总出度。
我们用weight数组来单独存储点权,用node来存储指向结点序号以及边权。
初始化之后开始模拟,遍历连通分支,需要获得每个连通分支的总边权重、最大点权结点下标、结点个数。
最后整理输出处理即可。

#include<bits/stdc++.h>
using namespace std;
const int maxx=2005;
struct node{
	int id,weight;
	node(int id,int weight){
		this->id=id;
		this->weight=weight;
	}
};
int n,k;
map<string,int> strToInt;
map<int,string> intToStr;
vector<node> vec[maxx];
vector<int> weight(maxx);
bool visit[maxx]={false};
int indext=0;
int exchange(string str){
	for(auto it=intToStr.begin();it!=intToStr.end();it++){
		if(it->second==str) return it->first;	
	}
	intToStr[indext]=str;
	strToInt[str]=indext;
	indext++;
	return indext-1;
}
void dfs(int x,int &kg,int &big,int &tot){
	visit[x]=true;
	tot++;
	if(weight[x]>weight[big]){
		big=x;
	}
	for(int i=0;i<vec[x].size();i++){
		int id=vec[x][i].id;
		int weight=vec[x][i].weight;
		kg+=weight;
		if(!visit[id]){
			dfs(id,kg,big,tot);
		}
	}
}
int main()
{
	cin>>n>>k;
	string a,b;
	int kg;
	for(int i=0;i<n;i++){
		cin>>a>>b>>kg;
		int id1=exchange(a);
		int id2=exchange(b);
		vec[id1].push_back(node(id2,kg));
		weight[id1]+=kg;
		weight[id2]+=kg;
	}
	map<string,int> mp;
	for(int i=0;i<indext;i++){
		if(!visit[i]){
			kg=0;
			int tot=0,big=i;
			dfs(i,kg,big,tot);
			if(kg>k&&tot>2){
				mp[intToStr[big]]=tot;
			}
		} 
	}
	cout<<mp.size()<<endl;
	if(mp.size()>0){
		for(auto it=mp.begin();it!=mp.end();it++){
			cout<<it->first<<' '<<it->second<<endl;
		}
	}
	return 0;
}

1076 Forwards on Weibo (30 分)

Weibo is known as the Chinese version of Twitter. One user on Weibo may have many followers, and may follow many other users as well. Hence a social network is formed with followers relations. When a user makes a post on Weibo, all his/her followers can view and forward his/her post, which can then be forwarded again by their followers. Now given a social network, you are supposed to calculate the maximum potential amount of forwards for any specific user, assuming that only L levels of indirect followers are counted.

Input Specification:

Each input file contains one test case. For each case, the first line contains 2 positive integers: N (≤1000), the number of users; and L (≤6), the number of levels of indirect followers that are counted. Hence it is assumed that all the users are numbered from 1 to N. Then N lines follow, each in the format:

M[i] user_list[i]
where M[i] (≤100) is the total number of people that user[i] follows; and user_list[i] is a list of the M[i] users that followed by user[i]. It is guaranteed that no one can follow oneself. All the numbers are separated by a space.

Then finally a positive K is given, followed by K UserID’s for query.

Output Specification:

For each UserID, you are supposed to print in one line the maximum potential amount of forwards this user can trigger, assuming that everyone who can view the initial post will forward it once, and that only L levels of indirect followers are counted.

Sample Input:

7 3
3 2 3 4
0
2 5 6
2 3 1
2 3 4
1 4
1 5
2 2 6

Sample Output:

4
5

思路

这道题最关键的就是审清题意吧,forward是转发的意思。
逆向建图,然后bfs求出前l层共有多少个结点(头结点除外)。
还是需要注意图和树的不同,图中可能有环,所以需要visit这个标记数组作用是不走回头路和对环的特殊处理,树的话没有环也不会走回头路。

#include<bits/stdc++.h>
using namespace std;
int n,l;
struct node{
	int id,level;
	node(int id,int level){
		this->id=id;
		this->level=level;
	}
};
const int maxx=1005;
vector<node> vec[maxx];
bool visit[maxx];
int bfs(int x){
	queue<node> que;
	int num=0;
	que.push(node(x,0));
	visit[x]=true;
	while(!que.empty()){
		node front=que.front();
		int id=front.id;
		que.pop();
		for(int i=0;i<vec[id].size();i++){
			node no=vec[id][i];
			if(!visit[no.id]&&front.level<l){
				no.level=front.level+1;
				visit[no.id]=true;
				num++;
				que.push(no);	
			}
		}
	}
	return num;
}
int main()
{
	cin>>n>>l;
	int a,b;
	for(int i=1;i<=n;i++){
		cin>>a;
		for(int j=0;j<a;j++){
			cin>>b;
			vec[b].push_back(node(i,0));
		}
	}
	int k;
	cin>>k;
	while(k--){
		memset(visit,false,sizeof(visit));
		cin>>a;
		int num=bfs(a);
		cout<<num<<endl;
	}
	return 0;
}

总结

浅浅总结一下,树就好比古代社会等级森严,节点间类似于上等人管理下等人,而下等人没办法管理上等人。而图则是现代社会人人平等,人人都有机会管理人人。
具体来说,对树而言节点能访问其孩子节点,而孩子节点没办法访问父节点,所以dfs或bfs是顺畅的,不会有冲突存在。
而对于图来说,可能有环的存在,且每个节点都可以访问和其相连的节点,这就可能产生冲突:
1.可能走了回头路,导致搜索不成功。这个问题好解决,设置一个visit数组即可解决此冲突。
2.对于环的处理要着重对待。
对于带权连通图,往往需要对边权+点权进行考察,那么边权在搜索中需要额外注意:
PTA-1034 Head of a Gang (30 分)这道题,对于一个连通分支的总权重是每一条边的权重之和。在搜索中,例如a->b这条边即使b点已经在之前被访问
(visit[b]==false)这条边的权仍然应该被计算在内。(这是在环中的搜索的考察)

for(int i=0;i<vec[x].size();i++){
	int id=vec[x][i].id;
	int weight=vec[x][i].weight;
	kg+=weight;
	if(!visit[id]){
		dfs(id,kg,big,tot);
	}
}

PTA-1076 Forwards on Weibo (30 分)这道题,对于每一层的序号,严格意义上是被访问过的节点的序号是确定的,因为一旦定下来一个根结点,那么每次bfs一层,这一层中的结点序号就确定下来,之后的bfs还可能访问到这个结点(环),但不应更改这个结点的层号。
下图这段代码:

for(int i=0;i<vec[id].size();i++){
	node no=vec[id][i];
	if(!visit[no.id]&&front.level<l){
		no.level=front.level+1;
		visit[no.id]=true;
		num++;
		que.push(no);	
	}
}

在晴神宝典中是这样描述的:

for(int i=0;i<vec[id].size();i++){
	node next=vec[id][i];
	next.layer=front.layer+1;
	if(!wether[next.id]&&next.layer<=l){
		que.push(next);
		wether[next.id]=true;
		num++;
	}
}

将层号赋值语句放到if前,虽然压入队列的一定是之前没有访问过的结点,相当于即使被访问过的结点层号修改了也不影响最后的值,但是这样写在逻辑上存在一定问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新西兰做的饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值