Page Hopping | Audiophobia | Caocao's Bridges

模板Page Hopping - UVA 821

模板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;
}
posted @ 2022-08-20 18:12  EdwinAze  阅读(40)  评论(0编辑  收藏  举报