洛谷·岛游记Island Travels

15 篇文章 4 订阅
15 篇文章 0 订阅

初见安~这里是传送门:洛谷P3070  [USACO13JAN]岛游记Island Travels 

题目描述

Farmer John has taken the cows to a vacation out on the ocean! The cows are living on N (1 <= N <= 15) islands, which are located on an R x C grid (1 <= R, C <= 50). An island is a maximal connected group of squares on the grid that are marked as 'X', where two 'X's are connected if they share a side. (Thus, two 'X's sharing a corner are not necessarily connected.)

Bessie, however, is arriving late, so she is coming in with FJ by helicopter. Thus, she can first land on any of the islands she chooses. She wants to visit all the cows at least once, so she will travel between islands until she has visited all N of the islands at least once.

FJ's helicopter doesn't have much fuel left, so he doesn't want to use it until the cows decide to go home. Fortunately, some of the squares in the grid are shallow water, which is denoted by 'S'. Bessie can swim through these squares in the four cardinal directions (north, east, south, west) in order to travel between the islands. She can also travel (in the four cardinal directions) between an island and shallow water, and vice versa.

Find the minimum distance Bessie will have to swim in order to visit all of the islands. (The distance Bessie will have to swim is the number of distinct times she is on a square marked 'S'.) After looking at a map of the area, Bessie knows this will be possible.

给你一张r*c的地图,有’S’,’X’,’.’三种地形,所有判定相邻与行走都是四连通的。我们设’X’为陆地,一个’X’连通块为一个岛屿,’S’为浅水,’.’为深水。刚开始你可以降落在任一一块陆地上,在陆地上可以行走,在浅水里可以游泳。并且陆地和浅水之间可以相互通行。但无论如何都不能走到深水。你现在要求通过行走和游泳使得你把所有的岛屿都经过一边。Q:你最少要经过几个浅水区?保证有解。

输入格式

* Line 1: Two space-separated integers: R and C.

* Lines 2..R+1: Line i+1 contains C characters giving row i of the grid. Deep water squares are marked as '.', island squares are marked as 'X', and shallow water squares are marked as 'S'.

输出格式

* Line 1: A single integer representing the minimum distance Bessie has to swim to visit all islands.

输入 

5 4 
XX.S 
.S.. 
SXSS 
S.SX 
..SX 

输出

3 

说明/提示

There are three islands with shallow water paths connecting some of them.

Bessie can travel from the island in the top left to the one in the middle, swimming 1 unit, and then travel from the middle island to the one in the bottom right, swimming 2 units, for a total of 3 units.

题解

这个题其实难度系数不高,但是细节比较多【其实也不多主要是我太蠢了所以出了很多细节bug】

题目意思就是给你一个图,‘X’的连通块是岛屿,‘S’是浅水,求走过每个岛屿至少一次最少需要走多少浅水。注意是最少一次!!翻译并没有体现这一点!!!上方原文中出现这一信息的地方已标黑。

所以我们先倒着想——要保证每个岛屿都走过,我们只能用到状态压缩来表示走过哪些岛屿。那么既然都状压了,直接上Hamilton求最短路咯~【传送门:专题·状态压缩

再往前看,Hamilton的计算有已知的邻接矩阵存各个点之间的距离。也就是说我们也需要处理出各个岛屿之间的距离。看到有大佬写长篇的SPFA求的,其实虽然我的做法复杂度比较高【反正能过就行了】,但是代码量少而且比较好想——从每个岛屿出发,bfs整张地图,遇到别的岛屿了就更新一遍dis。仅仅是这一步复杂度就有O(n^3)左右了吧【经实测,岛屿数量在20以内,n的范围是50】。而后因为本题要求的是“至少一次”, 所以用bfs得到的i到j的最短路和j到k的最短路其实并不一定就是Hamilton中i到k的最短路,所以再Floyd跑一遍,合乎题意【不跑的话会WA一组……其实做“至少一次”的Hamilton路径问题都是可以Floyd跑一遍来实现的,因为可以忽略中转用的岛屿】。这样一来任意两点之间的距离也处理出来了。

再往前似乎就没什么问题了呢。用bfs预处理出各个连通块并且标号就可以了呢。

也就是说两次bfs,一次Floyd,一个Hamilton,这个问题就解决啦。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, m, mp[60][60], dp[1 << 20][20], dis[40][40], dir[4][2] = {{0, 1}, {0, -1}, {-1, 0}, {1, 0}}, cnt = 0;
char g[60][60];
bool vis[60][60];
struct node {
	int x, y, stp;
	node() {}
	node(int xx, int yy) {x = xx, y = yy;}
	node(int xx, int yy, int ss) {x = xx, y = yy, stp = ss;}
};

bool in(int x, int y) {return 0 < x && x <= n && 0 < y && y <= m;}

void bfs1(int x, int y) {//找连通块 
	queue<node> q; q.push(node(x, y));
	while(q.size()) {
		node now = q.front(); q.pop(); mp[now.x][now.y] = cnt;//mp存陆地所在的岛屿编号 
		for(int i = 0; i < 4; i++) {
			register int tx = now.x + dir[i][0], ty = now.y + dir[i][1];
			if(in(x, y) && !mp[tx][ty] && g[tx][ty] == 'X') q.push(node(tx, ty)); 
		}
	}
}

void bfs2(int x) {//找最短路 
	memset(vis, 0, sizeof vis);
	queue<node> q;
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if(mp[i][j] == x) q.push(node(i, j, 0));
	//上方:属于这个岛屿的点都扔进去一起扩散 
	
	while(q.size()) {
		node now = q.front(); q.pop();
		for(int i = 0; i < 4; i++) {
			register int tx = now.x + dir[i][0], ty = now.y + dir[i][1];
			if(in(tx, ty) && g[tx][ty] != '.' && !vis[tx][ty]) {
				vis[tx][ty] = true;
				if(g[tx][ty] == 'S') q.push(node(tx, ty, now.stp + 1));//是浅水就直接now.stp + 1继续走 
				else dis[x][mp[tx][ty]] = dis[mp[tx][ty]][x] = min(dis[x][mp[tx][ty]], now.stp), q.push(node(tx, ty, now.stp));
				//上方:是陆地的话就判断一下最短路,继续扩散就行了,相当于“穿过这个岛屿” 
			}
		}
	}
}

signed main() {
//	freopen("in.txt", "r", stdin);
//	freopen("out.txt", "w", stdout);
	memset(dis, 0x3f, sizeof dis);
	n = read(), m = read();
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) cin >> g[i][j];
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if(g[i][j] == 'X' && !mp[i][j]) cnt++, bfs1(i, j);
	for(int i = 1; i <= cnt; i++) dis[i][i] = 0, bfs2(i);
	
	for(int k = 1; k <= cnt; k++) 
        for(int i = 1; i <= cnt; i++) 
            for(int j = 1; j <= cnt; j++) 
                dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
	//上方:Floyd

	memset(dp, 0x3f, sizeof dp);
	for(int i = 1; i <= cnt; i++) dp[1 << (i - 1)][i] = 0;

	for(int i = 0; i < (1 << cnt); i++) //岛屿还是从1开始编号,但是涉及到状态就一定要-1 【岛屿序号没有安排好……哎。 
		for(int j = 1; j <= cnt; j++) {
			if(i >> (j - 1) & 1) for(int k = 1; k <= cnt; k++) {
				if((i ^ 1 << (j - 1)) >> (k - 1) & 1) //虽然说的是至少一次,但dis已经Floyd切题了,这里就像普通的Hamilton一样就行了 
					dp[i][j] = min(dp[i][j], dp[i ^ 1 << (j - 1)][k] + dis[k][j]);
			}
		}
			
	
	int ans = 0x3f3f3f3f;
	for(int i = 1; i <= cnt; i++) ans = min(ans, dp[(1 << cnt) - 1][i]);
	printf("%d\n", ans);
	return 0;
}

/*
2 20
XXXSSSXSXSXSXSSSSSSS
S.X..X.SSX.....X..XS

ans:15
*/

真的调了好久……竟然连i <= n, j <= n这种错误都没发现……【应该是j <= m嘛】tcl。

迎评:)
——End——

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值