程序设计思维与实践 Week6 作业

A - 氪金带东

题意:
实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
思路:
该题是一个树结构,我们需要找到每一个点到其他点的最大距离。可以证明的是,树上一个点距离最远的点肯定会落在树的直径的一端。因此这个题需要用到树的直径。这个题大致分三步:

  1. 利用bfs,从任意一个点出发,求出树的直径的一个端点。
  2. 利用bfs,从已求得的树的直径的端点,求出树的直径的另一个端点
  3. 利用bfs,求出其他点与树的直径端点间的距离最大值,得到结果。

需要注意的是,如果直接从其他点求直径端点距离,需要很多次bfs。所以不如从树的直径端点求到其他点的距离,三步总的bfs次数只有三次。
总结:
刚开始是用的dfs的方法,因为递归的原因,无论是求距离还是求直径端点都贼麻烦,之后换成了bfs,真香。
代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <string.h>
using namespace std;
const int N = 1e5 + 1;
struct Edge
{
    int u, v, w, nxt;
} Edges[2*N];
int head[N], tot, max1, dis[N], vis[N], dis2[N]; //tot是Edge的下标
int d;
inline void init(int n)
{
    tot = 0;
    for (int i = 0; i < n; i++)
        head[i] = 0;
}
inline void addEdge(int u, int v, int w)
{
    tot++;
    Edges[tot].u = u;
    Edges[tot].v = v;
    Edges[tot].w = w;
    Edges[tot].nxt = head[u];
    head[u] = tot;
}
/*
int dfs(int u)
{
    for (int i = head[u]; i != -1; i = Edges[i].nxt)
    {
        if (!vis[Edges[i].v])
        {
            vis[Edges[i].v] = true;
            int max1 = dfs(Edges[i].v);
        }
        dis[Edges[i].v] = dis[temp] + Edges[i].w;
        if (max1 < dis[Edges[i].v])
        {
            max1 = dis[Edges[i].v];
            d = Edges[i].v;
        }
        
    }
    return max1;
}
*/
//该题比较适合使用bfs
//模拟正向过程
inline void bfs(int u)
{
    queue<int> q;
    d = u;
    max1 = 0;
    memset(dis, 0, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    vis[u] = true;
    q.push(u);
    while (!q.empty())
    {
        int temp = q.front();
        q.pop();
        for (int i = head[temp]; i; i = Edges[i].nxt)
        {
            if (!vis[Edges[i].v])
            {
                dis[Edges[i].v] = dis[temp] + Edges[i].w;
                vis[Edges[i].v] = true;
                q.push(Edges[i].v);
                if (max1 < dis[Edges[i].v])
                {
                    max1 = dis[Edges[i].v];
                    d = Edges[i].v;
                }
            }
        }
    }
}
int main()
{
    int n;
    while (scanf("%d", &n)==1)
    {
        tot = 0;
        for (int i = 1; i <= n; i++)
            head[i] = 0;
        for (int i = 2; i <= n; i++)
        {
            int v, w;
            scanf("%d%d", &v, &w);
            addEdge(i, v, w);
            addEdge(v, i, w);
        }
        bfs(1);
        //从一个最远端点开始bfs
        bfs(d);
        for (int i = 1; i <= n; i++)
            dis2[i] = dis[i];
        //从另一个最远端点开始bfs
        bfs(d);
        for (int i = 1; i <= n; i++)
            printf("%d\n", max(dis2[i], dis[i]));
    }
}

B - 戴好口罩!

题意:
新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
如果一个感染者走入一个群体,那么这个群体需要被隔离!
小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
危!!!
时间紧迫!!!!
需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。
众所周知,学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
请你编写程序解决!戴口罩!!

思路:
很明显,这个题就是求有共同联系的集合,所以需要用到并查集,求出0号元素所在集合的所有元素。
并查集主要有查找,合并两种操作。

  • 查找操作需要用到路径压缩。
  • 合并的时候要把小树合并到大树上,需要用一个rank数组记录集合大小。

总结:
并查集的写法一定要熟练掌握。
代码:

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
const int M = 5e2 + 1;
const int N = 3e4 + 1;
int par[N], ran[N];
void init(int n)
{
    for (int i = 0; i < n; i++)
        par[i] = i, ran[i] = 1;
}
int find(int x)
{
    return (par[x] == x) ? x : par[x] = find(par[x]);
}
bool unite(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x == y)
        return false;
    if (ran[x] > ran[y])
        swap(x, y);
    par[x] = y, ran[y] = (ran[x] += ran[y]);
    return true;
}

int main()
{
    int n, m;
    while (scanf("%d%d", &n, &m) == 2 && !(n == 0 && m == 0))
    {
        init(n);
        for (int i = 0; i < m; i++)
        {
            int num, last = -1;
            scanf("%d", &num);
            for (int j = 0; j < num; j++)
            {
                int x;
                scanf("%d", &x);
                if (last != -1)
                    unite(x, last);
                last = x;
            }
        }
        printf("%d\n", ran[find(0)]);
    }
    //system("pause");
}

C - 掌握魔法の东东 I

题意:
东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌溉
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)
东东为所有的田灌溉的最小消耗

思路:
为了最小的消耗,实际上就是求 水源+农田的图中的最小生成树。将水源点当作0号节点,求解最小生成树即可。
总结:
求解最小生成树的代码一定要掌握,已经达到标准的时候就要停止。
代码:

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 1e5 + 10;
int par[N], ran[N], tot, n;
struct Edge
{
    int u, v, w, nxt;
    bool operator<(const Edge a)
    {
        return w < a.w;
    }
} Edges[2 * N];

void init(int n)
{
    for (int i = 0; i <= n; i++)
        par[i] = i, ran[i] = 1;
}
int find(int x)
{
    return (par[x] == x) ? x : par[x] = find(par[x]);
}
bool unite(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x == y)
        return false;
    if (ran[x] > ran[y])
        swap(x, y);
    par[x] = y, ran[y] = (ran[x] += ran[y]);
    return true;
}
int kruskal()
{
    init(n);
    sort(Edges, Edges + tot);
    int cnt = 0, ans = 0;
    for (int i = 0; i < tot; i++)
    {
        if (unite(Edges[i].u, Edges[i].v)) //合法
        {
            ans += Edges[i].w;
            if (++cnt == n)
                return ans;
        }
    }
    return -1;
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &Edges[tot].w), Edges[tot].u = 0, Edges[tot].v = i, tot++;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            scanf("%d", &Edges[tot].w);
            if (i != j)
                Edges[tot].u = i, Edges[tot].v = j, tot++;
        }
    }
    printf("%d", kruskal());
    //system("pause");
}

D - 数据中心

题意:
在这里插入图片描述

思路:
题目很长,影响了读题。仔细读题后可以知道,该题就是要对一个加权无向图,求解一个最大边权最小的树结构,实际上就是最大值最小问题。
对于本题,利用最小生成树即可求解。因为kruskal开始将边的权值从小到大排了一次序,最后一个加入最小生成树的边一定是最大值最小的。
总结:
仔细读题,好好利用kruskal。这题学长不讲我绝对不知道咋做。。。。
代码:

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 1e5 + 10;
int par[N], ran[N], tot, n, m, root;
struct Edge
{
    int u, v, w, nxt;
    bool operator<(const Edge a)
    {
        return w < a.w;
    }
} Edges[2 * N];

void init(int n)
{
    for (int i = 0; i < n; i++)
        par[i] = i, ran[i] = 1;
}
int find(int x)
{
    return (par[x] == x) ? x : par[x] = find(par[x]);
}
bool unite(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x == y)
        return false;
    if (ran[x] > ran[y])
        swap(x, y);
    par[x] = y, ran[y] = (ran[x] += ran[y]);
    return true;
}
int kruskalMax()
{
    init(n);
    sort(Edges, Edges + tot);
    int cnt = 0, ans = 0;
    for (int i = 0; i < tot; i++)
    {
        if (unite(Edges[i].u, Edges[i].v)) //合法
        {
            ans = max(ans, Edges[i].w);
            if (++cnt == n - 1)
                return ans;
        }
    }
    return -1;
}
int main()
{
    scanf("%d%d%d", &n, &m, &root);
    init(n);
    for (int i = 0; i < m; i++)
        scanf("%d%d%d", &Edges[tot].u, &Edges[tot].v, &Edges[tot].w), tot++;
    printf("%d\n", kruskalMax());
    //system("pause");
}

总结:

这周主要复习了bfs,dfs,并查集,kruskal算法。在此基础上,学习了如何利用上述方法求树的直径。
重点:

  1. 会熟练的写bfs,dfs,并查集,kruskal算法的代码

  2. 看到复杂的题时,能够知道自己需要使用什么算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值