算法刷题day31

文章讲述了在DevCPP环境中编程实践中的问题解决,涉及BFS和DFS算法在奶牛选美、树重心计算和旅行费用优化中的应用。
摘要由CSDN通过智能技术生成

引言

这几天开始在 d e v c p p devcpp devcpp 上编写代码了,然后复制代码到 O J OJ OJ 上去测评,然后就是非常的不适应,代码环境、背景颜色、编译选项的一些设置啥的都很不好,而且也错的很多,主要是一些低级错误,所以说细节还是不到位,因为蓝桥杯是以最后一次提交为准,而且也不会给你任何反馈,所以说自己得提前适应这个环境和比赛规则,加油!


一、奶牛选美

标签:BFS

思路:题目要求从一个斑点到另一个斑点地最短距离,由于 N ≤ 50 N \leq 50 N50 ,所以我们可以直接枚举一块斑点中的所有的点,看最小的碰到斑点地最短距离是多少。具体做法,我是先枚举了一块斑点,在 s t st st 数组中标记了,然后遍历这块斑点的所有点,取第一次碰到另一块斑点(最短距离)的最小值即可。具体细节可以见代码。

题目描述:

听说最近两斑点的奶牛最受欢迎,约翰立即购进了一批两斑点牛。

不幸的是,时尚潮流往往变化很快,当前最受欢迎的牛变成了一斑点牛。

约翰希望通过给每头奶牛涂色,使得它们身上的两个斑点能够合为一个斑点,让它们能够更加时尚。

牛皮可用一个 N×M 的字符矩阵来表示,如下所示:

................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....
其中,X 表示斑点部分。

如果两个 X 在垂直或水平方向上相邻(对角相邻不算在内),则它们属于同一个斑点,由此看出上图中恰好有两个斑点。

约翰牛群里所有的牛都有两个斑点。

约翰希望通过使用油漆给奶牛尽可能少的区域内涂色,将两个斑点合为一个。

在上面的例子中,他只需要给三个 . 区域内涂色即可(新涂色区域用 ∗ 表示):

................
..XXXX....XXX...
...XXXX*...XX...
.XXXX..**..XXX..
........XXXXX...
.........XXX....
请帮助约翰确定,为了使两个斑点合为一个,他需要涂色区域的最少数量。

输入格式
第一行包含两个整数 N 和 M。

接下来 N 行,每行包含一个长度为 M 的由 X 和 . 构成的字符串,用来表示描述牛皮图案的字符矩阵。

输出格式
输出需要涂色区域的最少数量。

数据范围
1≤N,M≤50
输入样例:
6 16
................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....
输出样例:
3

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 100;

int n, m;
char g[N][N];
bool st[N][N];
int dist[N][N];

int dir[4][2] = {0,1,0,-1,1,0,-1,0};

void bfs1(PII S)
{
	st[S.first][S.second] = true;
	
	queue<PII> q; q.push({S.first, S.second});
	while(q.size())
	{
		auto t = q.front(); q.pop();
		
		for(int i = 0; i < 4; ++i)
		{
			int x = t.first + dir[i][0];
			int y = t.second + dir[i][1];
			if(x < 0 || x >= n || y < 0 || y >= m) continue;
			if(st[x][y] || g[x][y] == '.') continue;
			
			st[x][y] = true;
			q.push({x,y});
		} 
	}
}

int bfs2(PII S)
{
	memset(dist, -1, sizeof dist);
	dist[S.first][S.second] = 0;
	
	queue<PII> q; q.push({S.first, S.second});
	while(q.size())
	{
		auto t = q.front(); q.pop();
		
		for(int i = 0; i < 4; ++i)
		{
			int x = t.first + dir[i][0];
			int y = t.second + dir[i][1];
			if(x < 0 || x >= n || y < 0 || y >= m) continue;
			if(dist[x][y] != -1 || st[x][y]) continue;
			
			dist[x][y] = dist[t.first][t.second] + 1;
			if(g[x][y] == 'X') return dist[x][y];
			q.push({x,y});
		} 
	}
	
	return 1e9;
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> n >> m;
	for(int i = 0; i < n; ++i) cin >> g[i];

	for(int i = 0; i < n; ++i)
	{
		bool flag = false;
		for(int j = 0; j < m; ++j)
		{
			if(g[i][j] == 'X')
			{
				bfs1({i,j});
				flag = true; break;
			}
		}
		if(flag) break;
	}
	
	
	int res = 1e9;
	for(int i = 0; i < n; ++i)
	{
		for(int j = 0; j < m; ++j)
		{
			if(st[i][j])
			{
				res = min(res, bfs2({i,j}));
			}
		}
	}
	
	cout << res - 1 << endl;
	
	return 0;	
}

二、树的重心

标签:DFS

思路:题目要求找到树的重心的那个值,也就是删掉该点后的连通块中的最大值最小。那么我们可以一一枚举所有的点,然后找到连通块最大值最小的即可。我们用递归来做, d f s ( u ) dfs(u) dfs(u) 返回的是包括结点 u u u 在内的所有孩子的总和。那么我们先是遍历每一个孩子,递归找出他们的连通块,并且因为是从根结点开始递归的,对于该结点以上的结点,我们可以记录一个 s u m sum sum ,最后拿总和减去再加上所有孩子的连通块,就可以求出删除该结点的每一个连通块的数量,取最小值即可。更多细节见代码。

题目描述:

给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式
第一行包含整数 n,表示树的结点数。

接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。

输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围
1≤n≤105
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1e5+10, M = N * 2;

int n;
int h[N], e[M], ne[M], idx;
bool st[N];
int ans = 2e9;

void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int dfs(int u)
{
	st[u] = true;
	
	int sum = 1, size = 0;
	for(int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(st[j]) continue;
		
		int t = dfs(j);
		sum += t;
		size = max(size, t);
	}
	
	size = max(size, n - sum);
	ans = min(ans, size);
	
	return sum; 
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	memset(h, -1, sizeof h);
	cin >> n;
	for(int i = 0; i < n; ++i)
	{
		int a, b;
		cin >> a >> b;
		add(a,b), add(b,a);
	}
	
	dfs(1);
	
	cout << ans << endl;
	
	return 0;	
}

三、大臣的旅费

标签:树的直径、BFS

思路:要求花费最多,也就是求走的路最长, 然后根据数学关系就可以算出来了。求路最长,也就是求树的直径,公式就是先任意选取一个点,找到最远的点,然后从这个点开始再求一遍,这时最远的距离,就是它了。最远点我的可以用 B F S BFS BFS 做,在里面判断一下就行了,因为该图的边的权值都是正的且相等,所以能用 B F S BFS BFS

题目描述:

很久以前,T 王国空前繁荣。

为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T 国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。

同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J 是 T 国重要大臣,他巡查于各大城市之间,体察民情。

所以,从一个城市马不停蹄地到另一个城市成了 J 最常做的事情。

他有一个钱袋,用于存放往来城市间的路费。

聪明的 J 发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关。

具体来说,一段连续的旅途里,第 1 千米的花费为 11,第 2 千米的花费为 12,第 3 千米的花费为 13,…,第 x 千米的花费为 x+10。

也就是说,如果一段旅途的总长度为 1 千米,则刚好需要花费 11,如果一段旅途的总长度为 2 千米,则第 1 千米花费 11,第 
2 千米花费 12,一共需要花费 11+12=23。

J 大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式
输入的第一行包含一个整数 n,表示包括首都在内的 T 王国的城市数。

城市从 1 开始依次编号,1 号城市为首都。

接下来 n−1 行,描述 T 国的高速路(T 国的高速路一定是 n−1 条)。

每行三个整数 Pi,Qi,Di,表示城市 Pi 和城市 Qi 之间有一条双向高速路,长度为 Di 千米。

输出格式
输出一个整数,表示大臣 J 最多花费的路费是多少。

数据范围
1≤n≤105,1≤Pi,Qi≤n,1≤Di≤1000
输入样例:
5
1 2 2
1 3 1
2 4 5
2 5 4
输出样例:
135

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1e5+10, M = N * 2, INF = 0x3f3f3f3f;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int maxv = -1, maxid = 0;

void add(int a, int b, int c)
{
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void bfs(int S)
{
	memset(dist, -1, sizeof dist);
	dist[S] = 0;
	
	queue<int> q; q.push(S);
	while(q.size())
	{
		int t = q.front(); q.pop();
		
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			if(dist[j] != -1) continue;
			
			dist[j] = dist[t] + w[i];
			if(dist[j] > maxv) maxv = dist[j], maxid = j;
			q.push(j);
		}
	}
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	memset(h,-1,sizeof h);
	cin >> n;
	for(int i = 0; i < n - 1; ++i)
	{
		int a, b, c; cin >> a >> b >> c;
		add(a,b,c), add(b,a,c);
	}
	
	bfs(1);
	
	bfs(maxid);
	
	LL res = 10ll * maxv + (1ll + maxv) * maxv / 2;
	cout << res << endl;
	
	return 0;	
}

四、迷宫

标签:BFS

思路:就是一个 B F S BFS BFS 模板题,没啥说的,值得注意的是,这道题的起点或者终点也有可能走不了,所以需要特判一下。

题目描述:

一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由 n∗n 的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。

同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到
点B,问在不走出迷宫的情况下能不能办到。

如果起点或者终点有一个不能通行(为#),则看成无法办到。

注意:A、B不一定是两个不同的点。

输入格式
第1行是测试数据的组数 k,后面跟着 k 组输入。

每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。

接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。

再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。

注意到 ha,la,hb,lb 全部是从 0 开始计数的。

输出格式
k行,每行输出对应一个输入。

能办到则输出“YES”,否则输出“NO”。

数据范围
1≤n≤100
输入样例:
2
3
.##
..#
#..
0 0 2 2
5
.....
###.#
..#..
###..
...#.
0 0 4 0
输出样例:
YES
NO

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 110;

int T;
int n, m;
char g[N][N];
bool st[N][N];
PII S, E;

int dir[4][2] = {0,1,0,-1,1,0,-1,0};

bool bfs()
{
	if(g[S.first][S.second] == '#' || g[E.first][E.second] == '#') return false;
	memset(st, 0, sizeof st);
	st[S.first][S.second] = true;
	
	queue<PII> q; q.push(S);
	while(q.size())
	{
		auto t = q.front(); q.pop();
		
		for(int i = 0; i < 4; ++i)
		{
			int x = t.first + dir[i][0];
			int y = t.second + dir[i][1];
			if(x < 0 || x >= n || y < 0 || y >= n) continue;
			if(st[x][y] || g[x][y] == '#') continue;
			
			st[x][y] = true;
			q.push({x,y});
		}
	}
	return st[E.first][E.second];
}

int main()
{
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	
	cin >> T;
	while(T--)
	{
		cin >> n;
		for(int i = 0; i < n; ++i) cin >> g[i];
		cin >> S.first >> S.second >> E.first >> E.second;
		
		if(bfs()) puts("YES");
		else puts("NO");
	}
	
	return 0;	
}
  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lijiachang030718

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

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

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

打赏作者

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

抵扣说明:

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

余额充值