【PTA刷题】甲级 图

本文介绍了图的遍历,包括BFS和DFS的应用,如微博转发、城市争夺战等题目。强调了回路处理、节点访问标记、数据结构优化等关键点。同时,讲解了最短路径问题,如紧急情况下的最短路径模板,旅行计划和公共自行车管理。文章还探讨了最优子结构的重要性,并提供了Dijkstra算法和DFS的综合应用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图的遍历

1034 Head of a Gang. DFS遍历图


用时:70min
大意:给了N行通信(所以最多有两千个人->也就是节点),某几个人之间会形成社交网络。让你找出社交圈。社交圈是有条件的,比如边的权值要大于某个给定的K值。此外,社交圈的人数一定要大于两人。最后输出节点权值(该节点所有的边的权值之和)作为leader。还要输出这个社群的人数。

需要注意的事情:
1.社群中形成回路要如何遍历呢? 使用遍历过后马上删除这条边的方法
2.递归调用dfs之前,要先判断该节点是否被访问过。
3.翻译str和int用两个map来做。
4.最后结果Gang的输出。由于Gang是个map,所以只能用迭代器输出。it->first输出第一个参数(在本题中即是key值),it->second输出第二个参数(在本题中即是成员数量)。
**5.写大型题目要记得写注释,以及写之前先把逻辑模块化。**不然必出错。

#include <iostream>
#include <vector>
#include <map>
#include <string>
using namespace std;

const int maxpeople = 2005;
int graph[maxpeople][maxpeople] = {
   
   0};
bool viewed[maxpeople] = {
   
    false };
int numPeople = 0; //编号累加,总人数

vector<int> path; //当前集群的路径长度数组(公用)
int member = 0;//当前集群的成员数量

map<string, int> strToint;
map<int, string> intTostr;//两个转化用的MAP

map<string, int> Res;//结果
int totalWeight[maxpeople] = {
   
    0 }; //每个节点的权值
int head;//gang's leader

int change(string str)
{
   
   
	if (strToint.find(str) != strToint.end())
	{
   
   
		return  strToint[str];
	}
	else
	{
   
   
		strToint[str] = numPeople;
		intTostr[numPeople] = str;
		return numPeople++;
	}
}
void makeG(string A,string B,int val)
{
   
   
	int u = change(A), v = change(B);
	graph[u][v] += val;
	graph[v][u] += val;
}
void caculateWeight(int n)
{
   
   	
	
	for (int i = 0; i < n; i++)
	{
   
   
		for (int j = 0; j < n; j++)
		{
   
   
			totalWeight[i] += graph[i][j];
		}
	}
}
void DFS(int u,int n,int &head) //u为当前顶点标号,n为节点总数
{
   
   	
	member++;
	viewed[u] = true; //当前节点被访问

	for (int v = 0; v < numPeople; v++)
	{
   
   
		if (graph[u][v] > 0)//有路就行,不用未访问
		{
   
   	
			path.push_back(graph[u][v]); //推入当前路径
			
			graph[u][v] = graph[v][u] = 0;//删除路径,防止回头!!!
			if (totalWeight[head] < totalWeight[u])
				head = u;
			if(viewed[v] == false) //如果v节点未被访问,则递归访问
				DFS(v, numPeople,head);
		}
	}
}

int DfsAllGraph(int n,int K) //遍历所有连通块
{
   
   	
	int cluster = 0;
	int sum = 0;
	for (int u = 0; u < n; u++)
	{
   
   
		if (viewed[u] == false)
		{
   
   	
			DFS(u, n,u); //访问u的连通块,先默认u就是leader

			for (int i = 0; i < path.size(); i++)
				sum += path[i]; //计算权值之和
			if (sum > K && member > 2)
			{
   
   
				cluster++;
				Res[ intTostr[u] ] = member; //leader的姓名对应成员数量储存
			}
			sum = 0;
			member = 0;
			path.clear(); //重新初始化
		}
	}

	return cluster;
}
int main()
{
   
   
	int N, K; //N条通信
	cin >> N >> K;
	for (int i = 0; i < N; i++)
	{
   
   
		string A, B;
		int v;
		cin >> A >> B>>v;
		makeG(A, B, v); //把数字填入Graph
	}

	caculateWeight(numPeople); //计算每个节点的权值
	cout << DfsAllGraph(numPeople, K)<<endl;
	for (auto it = Res.begin(); it != Res.end(); it++)
		cout << it->first << " " << it->second << endl;

	return 0;

}

1076 Forwards on Weibo. BFS遍历图


用时:55min
本题最大的难度在读题。一定要分清follows和followed。前者是关注,后者是转发。
一个用户的转发或原创只能被自己的粉丝看到,所以是个有向图。题目中给的数据是一个人关注的目标。所以建图的时候是由target指向本节点i。

需要注意的点:
由于多次check的节点起点不同,所以每次check完之后要把viewed监控是否访问数组重置。

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

const int MaxPeople = 1010;
vector<int> G[MaxPeople];
bool viewed[MaxPeople] = {
   
    false }; //是否方位数组
//int times = 0;

int bfs(int u, int N, int times, int level) //当前节点,总人数,总转发次数
{
   
   
	if (G[u].size() == 0) //如果这个人一个粉丝都没有,直接G
		return times;
	queue<int> Q;
	Q.push(u);
	viewed[u] = true; //u被访问过了
	int p; //临时节点
	int curlevel = 0;
	while (!Q.empty() && curlevel++ < level)  //Q不空,且当前层数不超过level
	{
   
   	
		int level_size = Q.size();
		
		for (int l = 0; l < level_size; l++)  //遍历当层
		{
   
   
			p = Q.front(); Q.pop();
			for (int i = 0; i < G[p].size(); i++) //p能到达的所有点
			{
   
   
				int v = G[p][i]; //p能到达的其中一个节点
				if (viewed[v] == false)//如果这个v点没有被访问过
				{
   
   
					Q.push(v);//入队
					viewed[v] = true; //v从此被访问过了
					times++; //总转发次数加一
				}
			}
		}
	}
	return times; //返回结果转发次数
}

void InitialViewed(int N)  //重新初始化viewed
{
   
   
	for (int i = 1; i <= N; i++)
		viewed[i] = false;
}

int main()
{
   
   
	int N, L;
	cin >> N >> L;
	
	for (int i = 1; i <= N; i++)//人数从1~N,建图
	{
   
   
		int follows;
		cin >> follows;
		for (int j = 0; j < follows; j++)
		{
   
   
			int target;
			cin >> target;
			G[target].push_back(i); //关注的目标有了i这个粉丝
		}
	}

	int checknum;
	cin >> checknum;
	for (int i = 0; i < checknum; i++)
	{
   
   
		int checkone;
		cin >> checkone;
		cout<<bfs(checkone, N, 0, L)<<endl; //bfs起点,总节点数,起始转发次数,规定最大层数

		InitialViewed(N);//check完一次,重置viewed
	}

	return 0;
}

1013 Battle Over Cities 25points


用时:60min (一开始思路不太对,没有想到集群数量和最少连线的关系)
大意:
给你一个图,让你从中去掉一个节点,然后判断要最少连多少条路可以使图变成连通图。

思路:
其实就是让你在DFS里面的时候,忽略掉一个节点(以及他的所有通路),来判断集群数量。
只需要在DFS内部的判断条件中加入V != LostCity即可。在DFS里面,每次进入一次if(相当于完成了一个集群的遍历,进入下一个集群),就让cluster集群数量加1即可。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <queue>
#include <stdio.h>

using namespace std;

/*需要添加的最小边数,就是去掉lostcity之后的集群数量-1*/
const int MaxCities = 1005;
int G[MaxCities][MaxCities]; 
bool viewed[MaxCities] = {
   
    false };

void dfs(int u,int N,int lostcity) //u为起点,N为节点总数
{
   
   
	viewed[u] = true;//标记当前访问节点

	for (int v = 1; v <= N; v++)
	{
   
   
		if (v != lostcity && viewed[v] == false && G[u][v]) //把lostcity排除出去
		{
   
   
			viewed[v] = true;
			dfs(v, N, lostcity);
		}
	}
}

void Initialviewed(int N,int lostcity) //重置viewed,并优先排除lostcity
{
   
   
	for (int i = 1; i <= N; i++)
		viewed[i] = false;
	viewed[lostcity] = true;
}

int dfsAll(int N,int lostcity,int clusters) //去掉Lostcity之后,全图遍历
{
   
   
	Initialviewed(N, lostcity); //初始化viewed

	for (int u = 1; u <= N; u++)
	{
   
   
		if (u != lostcity && viewed[u] == false) //非lostcity的未访问节点
		{
   
   	
			clusters++;
			dfs(u, N, lostcity);
		}
	}
	return clusters;
}

int main()
{
   
   
	int N, M, K;
	//cin >> N >> M >> K; 
	scanf("%d %d %d\n",&N,&M,&K);//N总节点数,M路数,K检查的点数量
	for (int i = 0; i < M; i++)
	{
   
   
		int t1, t2;
		//cin >> t1 >> t2; 
		scanf("%d %d\n", &t1, &t2);//两城市之间有路
		G[t1][t2] = G[t2][t1] = 1;
	}

	for (int 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值