Page Hopping | Audiophobia | Caocao's Bridges
模板Page Hopping - UVA 821
题目
二基楼有n个教室(1≤n≤100),任意两间教室之间可能存在多条路径(一个你所不知道的世界)。作为一个作死的冒险家,你想走遍所有路径以得到任意两间教室之间最短路径的平均值。最后你得到了结果满意地在二基楼迷了路。
多组数据。
每组数据为一行。一组数据包含多个整数对a、b(1≤a, b≤100),表示可以从教室a到教室b。当输入“0 0”时,表示结束该组数据的输入。
最后输入“0 0”,表示整个输入的结束。
每组数据输出一行。以Sample Output的格式输出任意两教室最短距离的平均值,保留3位小数。
input
1 2 2 4 1 3 3 1 4 3 0 0
1 2 1 4 4 2 2 7 7 1 0 0
0 0
output
Case 1: average length between pages = 1.833 clicks
Case 2: average length between pages = 1.750 clicks
hits
第一组数据中,路径有12条,其总的最短路径长度为22,则平均值为22/12=1.833
思路
求所有最短路径长度的平均值, 那就要知道每个点能去的点数量(路径数)和每个点能去的点之间的路径和。
显然我们需要多次求两个点之间的最短路径, 最符合的算法就是 floyd 求多源最短路。
求出来最短路之后就再次遍历所有点, 若他们之间的最短路径存在则说明是一个成立的路径, 用这种方法来求出所有路径数。
最后输出即可。
代码
// Made by Aze //
//------------------------------//
/*
* problem:https://vjudge.csgrandeur.cn/problem/UVA-821#author=SCU2018
* date:8/17 2:48PM
*
*/
//------- Include Area----------//
#include <iostream>
#include <iomanip>
#include <cstring>
#include <queue>
#include <algorithm>
#include <vector>
using namespace std;
int readInt()
{
int t;
cin >> t;
return t;
}
//------- Coding Area ---------//
const int N = 1010, INF = 0x3f3f3f3f;
int dist[N][N];
int n;
void floyd()
{
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
int main()
{
cin.tie(0)->sync_with_stdio(false);
cout << fixed << setprecision(3); // 浮点数精度设置为保留小数点后3位
int a, b;
int kase = 1;
while (cin >> a >> b && (a || b))
{
memset(dist, INF, sizeof dist);
do
{
dist[a][b] = 1;
n = max(n, a);
n = max(n, b);
} while (cin >> a >> b && (a || b));
floyd();
double res = 0;
int cnt = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (j != i && dist[i][j] != INF)
{
cnt++;
res += dist[i][j];
}
}
}
cout << "Case " << kase++ << ": average length between pages = " << res / (double)cnt << " clicks\n";
}
return 0;
}
多源路径最大值最小Audiophobia
题目
多源路径最大值最小Audiophobia
Alice喜欢冒险。现在Alice在一座小岛上,这座岛有许多站,各站之间有些存在着通路,但通路中间有石墙阻挡。Alice可以借助自己随身携带的绳索翻过石墙从而能从一站到达另一站。绳索的长度为L,则所有高度不大于L的石墙Alice都可以翻过。不过绳索是特种工具,太长的绳索重量很大难以携带,Alice希望能尽量短地拿一根绳索并到达目的地。给出Alice的起点和终点,帮助Alice确定最短能完成冒险的绳索长度。
多组数据。
每组数据有多行,第一行是三个用空格分隔的数字C,S,Q,分别表示有C个小站,S条通路和Q次冒险。接下来有S行,每行有三个数字由空格隔开,分别是c1,c2,d,表示从c1站到c2站(c1 ≠ c2)有通路,并且中间有高为d的石墙。接下来Q行,一行中给出两个整数c1,c2,表示Alice的探险要从c1到c2(c1 ≠ c2)。
数据最后一行C,S,Q都是0,表示数据输入结束,这一组数据不需要处理
- C ≤ 100
- S ≤ 1000
- Q ≤ 10000
- 0 < d ≤ 100000
- 1 ≤ c1,c2 ≤ C
对于每组数据,先在一行中输出编号(从1开始),接下来输出Q行,输出一个数字表示这一个冒险中Alice可带的最短绳子的长度,若Alice选的这两站之间不能连通,则输出"no path"。
每两组数据之间用一个空行隔开。
input
7 9 3
1 2 50
1 3 60
2 4 120
2 5 90
3 6 50
4 6 80
4 7 70
5 7 40
6 7 140
1 7
2 6
6 2
7 6 3
1 2 50
1 3 60
2 4 120
3 6 50
4 6 80
5 7 40
7 5
1 7
2 4
0 0 0
output
Case #1
80
60
60
Case #2
40
no path
80
思路
之前做过单源路径最小值最大的题, 用的dijkstra。
这里则是多源求路径最大值最小, 用floyd。
具体则是将原来floyd中
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
换成
dist[i][j] = min(dist[i][j], max(dist[i][k], dist[k][j])
模板中 i->j 的总路径等于 i->k + k->j, 这里则是 i->j 的所有路径中最大权值 的最小值为 "i->k 和 k->j 的所有路径最大值的最小值" 的最大值
为什么需要用到max呢? 因为如果用min的话, 得到的就不是i->k->j 这条路径中的最大权值, 也就无法得出结果。
代码
//------------------------------//
// Made by Aze //
//------------------------------//
/*
* problem:https://vjudge.csgrandeur.cn/contest/510867#problem/B
* date:8/17 3:23PM
*
*/
//------- Include Area----------//
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <vector>
using namespace std;
int readInt()
{
int t;
cin >> t;
return t;
}
//------- Coding Area ---------//
const int N = 1e3 + 10, INF = 0x3f3f3f3f;
int n, m, q;
int dist[N][N];
void floyd()
{
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dist[i][j] = min(dist[i][j], max(dist[i][k], dist[k][j]));
}
int main()
{
cin.tie(0)->sync_with_stdio(false);
int kase = 1;
while (cin >> n >> m >> q && (n || m || q))
{
if (kase > 1)
cout << "\n";
cout << "Case #" << kase++ << endl;
memset(dist, INF, sizeof dist);
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
dist[b][a] = dist[a][b] = min(dist[a][b], c);
}
floyd();
while (q--)
{
int a, b;
cin >> a >> b;
if (dist[a][b] <= INF / 2)
cout << dist[a][b] << endl;
else
cout << "no path\n";
}
}
}
Caocao's Bridges
https://vjudge.csgrandeur.cn/problem/HDU-4738
在赤壁之战中,曹操被诸葛亮和周瑜击败。但他不会放弃。曹操的军队仍然不善于水战,所以他提出了另一个想法。他在长江建造了许多岛屿,在这些岛屿的基础上,曹操的军队很容易攻击周瑜的部队。曹操还建造了连接岛屿的桥梁。如果所有岛屿都通过桥梁相连,那么曹操的军队可以在这些岛屿中非常方便地部署。周瑜无法忍受,所以他想要摧毁一些曹操的桥梁,这样一个或多个岛屿就会与其他岛屿分开。但周瑜只有一枚由诸葛亮留下的炸弹,所以他只能摧毁一座桥。周瑜必须派人携带炸弹来摧毁这座桥。桥上可能有守卫。轰炸队的士兵数量不能低于桥梁的守卫数量,否则任务就会失败。请弄清楚周瑜至少需要多少士兵。
测试用例不超过12个。
在每个测试用例中:
第一行包含两个整数N和M,意味着有N个岛和M个桥。所有岛都从1到N编号。(2 <= N <= 1000,0 <M <= N²)
接下来的M行描述了M个桥。每条线包含三个整数U,V和W,意味着有一个连接岛U和岛V的桥,并且在该桥上有W守卫。(U≠V且0 <= W <= 10,000)
输入以N = 0且M = 0结束。
对于每个测试用例,输出周瑜完成任务所需的最少士兵数量。如果周瑜无法成功,请输出-1。
input
3 3
1 2 7
2 3 4
3 1 4
3 2
1 2 7
2 3 4
0 0
output
-1
4
思路
(假设你已经做过3道以上的tarjan有向图的题, 不然还是先去做之前的训练吧, 或者查阅算法入门经典训练指南的P312页)
该图为无向图, 对于样例1来说, 是一个环, 此时无论摧毁哪条边都还是连通, 样例2则只是连通而非环, 此时可以找到最小的边摧毁。
就像之前有向边的tarjan一样, 把相互连通成环的缩小成一个点, 此时剩下的边就是摧毁后能断开的“桥”。
有向边的tarjan中需要用栈来保存当前路径, 因为只是到之前访问过的点并不一定能构成连通(有来无回)。
这里因为是无向边, 则只要是连通到之前的点就一定连通, 且这个之前的点不能是父节点。
在无向连通图 \(G\) 的 \(DFS\) 树中, 非根节点 \(u\) 是 \(G\) 的割顶当且仅当 \(u\) 存在一个子节点 \(v\), 使得 \(v\) 及其所有后代都没有反向边连回 \(u\) 的祖先(连到 \(u\) 不算)。
就是 \(low(v) >= dfn(u)\)
桥: 若 \(v\) 的后代只能连回 \(v\)自己(即 \(low(v) > dfn(u)\)), 只需删除 \((u,v)\) 这条边就可以让图 \(G\)非连通, 这个\((u,v)\)就是桥。
所以就在tarjan中加一条:
if(!dfn[j])
{
tarjan(j, i ^ 1); // 第二个参数传入反向边的idx
low[u] = min(low[u], low[j]);
if(low[j] > dfn[u])
res = min(res, w[i]); // w[i] 为该边的权值
}
else if(i != fa) // 若当前idx不是反向边就更新
low[u] = min(low[u], dfn[j]);
这里 i = idx
如果 idx 为奇数, 则反向边为 idx - 1 = idx^1
如果 idx 为偶数, 则反向边为 idx - 1 = idx ^ 1
我们并不需要求出强连通分量的根节点之类的东西, 故仅仅以上就够。
最后得出需要判断该图是否连通, 如果不连通(进行了超过一次tarjan)就输出0, 因为不需要炸桥。
如果最后 res == 0
则需要输出 1, 因为至少需要一个炮兵拿炸弹。
代码
//------------------------------//
// Made by Aze //
//------------------------------//
/*
* problem:https://vjudge.csgrandeur.cn/problem/HDU-4738
* date:8/20 4:35PM
*
*/
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e3 + 10, M = 2e6 + 10, INF = 0x3f3f3f3f;
int n, m;
int h[N], e[M], ne[M], w[M], idx;
bool bridge[M];
int dfn[N], low[N], timestamp;
int res;
void init()
{
memset(h, -1, sizeof h);
memset(e, 0, sizeof e);
memset(ne, 0, sizeof ne);
memset(dfn, 0, sizeof dfn);
memset(low, 0, sizeof 0);
memset(w, 0, sizeof w);
timestamp = idx = 0;
res = INF;
}
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void tarjan(int u, int fa)
{
dfn[u] = low[u] = ++timestamp;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j, i ^ 1);
low[u] = min(low[u], low[j]);
if (low[j] > dfn[u]) // 如果当前j最多只能连回到j自己, 说明u->j就是一个桥
res = min(res, w[i]);
}
else if (i != fa) // 避免反向边
low[u] = min(low[u], dfn[j]);
}
}
int main()
{
cin.tie(0)->sync_with_stdio(false);
while (cin >> n >> m && n + m)
{
init();
int cnt = 0; // 是否连通
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
{
tarjan(i, -1);
cnt++;
}
if (cnt > 1) // 不连通就不用拆
cout << 0 << endl;
else if (res == 0x3f3f3f3f)
cout << -1 << endl;
else if (res == 0)
cout << 1 << endl;
else
cout << res << endl;
}
return 0;
}