P2941 [USACO09FEB]Surround the Islands S 传送门
感觉其他有些题解有点简略,wtcl,一开始理解不了题意,我现在就写篇详细点点的吧。
题目分析
-
我们最开始输入的 n n n 条边 ( u , v ) (u,v) (u,v) 都是可以直接以 0 费用从 u 到 v 的。
也就是题目中的走路,即 u 和 v 是同一岛上的不同顶点。
-
接着输入的是从第 i 个点乘船到达其他 i(包括 i 点自己)所需要的费用。
求解
概述:为了保证最小费用,我们当然是走路到达同一岛上的点,乘船到达不同岛上的点。
不同岛:开始没有给出连边的两个点。
1. 既然已经确定了我们只会把费用花在乘船上,那我们怎么最小化乘船费用呢?
我们可以选择两岛中相隔近的两顶点作为当前到达两岛的出发点以及终点。
相隔近,也就是费用小。
所以我们可以开数组记录两岛之间,到达所需的费用,这样,我们最后只需要统计看看哪个岛到其他岛的总费用最小,输出总费用的两倍(题中说了要往返)即可。
2. 如何确定岛?
我们已知岛上任意两点可以互相到达(0 费用),任意两岛不可直接互达(需要花费钱)。
那这不就是强联通分量(算法笔记)吗?
在任意一个强连通分量中,任意两点我们可以 0 费用到达。所以我们只需要在最开始输入边的时候建边,然后跑一边 Tarjan,确定所有岛即可。
注意建边要双向边,因为岛上任意两顶点是互达的。
代码(更多细节见代码)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 505;
int n, m;
int cnt, hd[maxn];
struct node{
int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn];
int tmp, top;
int st[maxn];
int co[maxn], col;
bool vis[maxn];
int ans;
void add (int u, int v)
{
e[++cnt].to = v;
e[cnt].nxt = hd[u];
hd[u] = cnt;
}
void tarjan (int u)//跑 Tarjan 确定岛
{
dfn[u] = low[u] = ++tmp;
st[++top] = u;
for (int i = hd[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (!dfn[v])
{
tarjan (v);
low[u] = min (low[u], low[v]);
}
else if (!co[v]) low[u] = min (low[u], dfn[v]);
}
if (low[u] == dfn[u])
{
co[u] = ++col;
while (st[top] != u)
{
co[st[top]] = col;
top--;
}
top--;
}
}
int dis[maxn][maxn];
signed main ()
{
scanf ("%d", &m);
for (int i = 1; i <= m; i++)
{
int u, v;
scanf ("%d %d", &u, &v);
add (u, v), add (v, u);//双向建边
n = max (n, max (u, v));
}
for (int i = 1; i <= n; i++)
{
if (!dfn[i]) tarjan (i);
}
for(int i = 1; i <= col; i++)
for(int j = 1; j <= col; j++) dis[i][j] = 2147483647;//记得要初始化
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
int t;
scanf ("%d", &t);
dis[co[i]][co[j]] = min (dis[co[i]][co[j]], t);//两岛间到达所需最小的费用
}
ans = 2147483647;
for (int i = 1; i <= col; i++)
{
int res = 0;
for (int j = 1; j <= col; j++)
{
if (i == j) continue;
res += dis[i][j];
}
ans = min (ans, res);//统计答案
}
printf ("%d\n", ans * 2);//往返
return 0;
}
感谢阅读题解 (啊求赞) ~
如有问题求大佬斧正。