Week6 作业 A-氪金带东 B-戴好口罩 C - 掌握魔法の东东 I D-数据中心

A-氪金带东

题目描述

实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
样例
提示: 样例输入对应这个图,从这个图中你可以看出,距离1号电脑最远的电脑是4号电脑,他们之间的距离是3。
4号电脑与5号电脑都是距离2号电脑最远的点,故其答案是2。5号电脑距离3号电脑最远,故对于3号电脑来说它的答案是3。同样的我们可以计算出4号电脑和5号电脑的答案是4.

输入输出格式及样例

Input
输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。
Output
对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N).
Input
5
1 1
2 1
3 1
1 1
Output
3
2
3
4
4

思路

对于本题很容易想到用图来保存每台电脑之间的联系,挨个bfs求出任意两个点之间的最远距离,即可求出每台电脑所能到达的最远距离。
但是很明显复杂度太大了,会超时。所以换一种思路,我们想到了用数的直径来转换,每个点到数的两个直径端点的距离最大值就是他所能到达的最远距离。
数的直径的求法,任取一个点进行bfs得到距离最远的一个点是直径的其中一个点,再拿这个点进行一次bfs的话得到的最远点就是直径的另一个端点。如果我们在bfs的途中保存每个点到另一个点的最远距离的话,我们只需要用直径的另一个端点再进行一次bfs得到的数组,和上一次用前一个端点进行bfs得到的最远距离数组进行max比较,就能得到每个点所能到达的最远距离。

实验代码

#include <iostream>
#include <stdio.h>
#include <queue> 
#include <cstring>
using namespace std;
const int maxn = 100001;
struct Edge{//边
    //int u;
    int v;
    int w;
    int nxt;
}edge[maxn];
int head[maxn], tot;//链式前向星
void init(){
    tot = 0;
    memset(head, -1, sizeof(head));
}
void add(int u, int v, int w){
    //edge[tot].u = u;
    edge[tot].v = v;
    edge[tot].w = w;
    edge[tot].nxt = head[u];
    head[u] = tot;
    tot++;
}
bool vis[maxn];
int dis[maxn], vi, a, b, sum;
int dis2[maxn];
int bfs(int a){
    queue<int> q;
    memset(dis, 0, sizeof(dis));
    memset(vis, false, sizeof(vis));
    vis[a] = true;
    sum = 0;
    vi = a;
    q.push(a);
    while (!q.empty()){
        int b = q.front(); q.pop();
        for (int i = head[b]; i != -1; i = edge[i].nxt){
            if (vis[edge[i].v] == false){
                vis[edge[i].v] = true;
                dis[edge[i].v] = dis[b] + edge[i].w;
                if (sum < dis[edge[i].v]){
                    sum = dis[edge[i].v];
                    vi = edge[i].v;
                }
                q.push(edge[i].v);
            }
        }
    }
    return vi;
}
int main(){
    int n, v, w;
    while (~scanf("%d", &n)){
        init();
        for (int i = 2; i <= n; i++){
            scanf("%d%d", &v, &w);
            add(i, v, w);
            add(v, i, w);
        }
        a = bfs(1);
        b = bfs(a);//从直径的第一个点开始bfs
        for (int i = 1; i <= n; i++)
            dis2[i] = dis[i];
        bfs(b);//从直径的第二个点开始bfs
        for (int i = 1; i <= n; i++)
            cout << max(dis[i], dis2[i]) << endl;//两次遍历得到直径的两个点到每个点的最远距离,两者的最大值就是每个点能到达的最远距离
    }
    return 0;
}

B-戴好口罩

题目描述

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

输入输出格式及样例

Input
多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
学生编号为0~n-1
小A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
Output
输出要隔离的人数,每组数据的答案输出占一行
Input
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
Output
4
1
1

思路

本题查找和AA直接或间接接触过的人的数量,就是一种分组求从属关系的题,对于这种题,我们采用并查集的方式,将所有学生分组,并查集数组保存的是每个节点所属集合的老大,初始为自己,然后把团体两两合并,合并就是更新每个并查集数组结点的老大,将前者的老大更新为后者的老大,或者把后者的老大更新为前者的老大,都可以,都是把两个集合归并为一个集合,但是为了加快搜索时间,将数量少的集合的老大更新为数量大的集合的老大,可以减少查询时的递归次数和压缩路径次数,把所有学生分组后查询AA同学所在的分组的数量即可。

实验代码

#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
const int maxn = 3e4 + 1;
int par[maxn], rnk[maxn];
void init(int n) {
	for (int i = 0; i < n; i++) {
		par[i] = i;
		rnk[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 (rnk[x] > rnk[y])swap(x, y);
	par[x] = y;
	rnk[y] += rnk[x];
	return true;
}
int main(){
	int n, m;
	while (~scanf("%d%d", &n, &m)){
		if (n == 0 && m == 0)break;
		init(n);
		for (int i = 0; i < m; i++){
			int count;
			scanf("%d", &count);
			int* a = new int[count];
			for (int i = 0; i < count; i++){
				scanf("%d", &a[i]);
				if (i > 0) unite(a[i], a[i - 1]);
			}
		}
		cout << rnk[find(0)] << endl;
	}
	return 0;
}

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)
东东为所有的田灌氵的最小消耗

输入输出格式及样例

Input
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
Output
东东最小消耗的MP值
Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Output
9

思路

  先说说求最小生成树,最小生成树的求取办法之一是kruskal算法,过程是,先把图中的每条边按权值大小排序。小的在前,然后依次取出最小的边加入到最小生成树中但是要避免构成回路,所以再插入的时候进行判定,判定插入的边的两个端点是否都已经属于最小生成树的结点集合中,这就是第二题说过的从属问题,使用并查集判断两个端点的集合老大是否相等,不相等说明两个点不都在最小生成树中,可以插入,然后将两个点合并,最后当插入边的数量等于点数-1就得到了最小生成树。
  本题如果去掉引水的功能,就变成了一个最小生成树的问题,用最少的消耗使整个图连通,不就是最小生成树问题吗,但是有引水功能就复杂了起来,为了把他转换为最小生成树问题,我们把“黄河之水天上”来转换为将黄河与农田之间建立传送门,建立传送门的消耗就是引水的消耗,这就是加了一个点的最小生成树问题,加了一个超级源点(黄河)。

实验代码

#include<iostream>
#include<stdio.h>
#include<algorithm> 
using namespace std;
const int maxn = 3e2 + 1;
struct edge{//边
	int u;
	int v;
	int w;
	bool operator<(const edge& b)const{return w < b.w;}
}edge[maxn * maxn];

int par[maxn], rnk[maxn];//并查集
void init(int n) {
	for (int i = 0; i < n; i++) {
		par[i] = i;
		rnk[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 (rnk[x] > rnk[y])swap(x, y);
	par[x] = y;
	rnk[y] += rnk[x];
	return true;
}

int kruskal(int n1,int n2){
	init(n1 + 1);
	sort(edge, edge + n2);
	int sum = 0;
	int count = 0;
	for (int i = 0; i < n2; i++){
		if (unite(edge[i].u, edge[i].v) != false){
			sum += edge[i].w;
			count++; 
		}
		if (count == n1)
			return sum;
	}
}
int main(){
	int n1;
	cin >> n1;
	int n2 = 0;
	for (int i = 1; i <= n1; i++){//建立超级源点
		scanf("%d", &edge[n2].w);
		edge[n2].u = 0;
		edge[n2++].v = i;
	}
	for (int i = 1; i <= n1; i++){
		for (int j = 1; j <= n1; j++){
			scanf("%d", &edge[n2].w);
			if (i == j) continue;
			edge[n2].u = i;
			edge[n2++].v = j;
		}
	}
	int ans = kruskal(n1, n2);
	cout << ans << endl;
	return 0;
}

D-数据中心

题目描述

在这里插入图片描述

输入输出格式及样例

这里是引用
Input
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
Output
4

样例分析

这里是引用

思路

从题目描述中,我们要求出完成任务时间最短的路径安排,这又是一个最小生成树问题。
但同时要我们输出这个最小生成树中的最大权值边,这很简单,因为根据kruskal算法,每条边都是插入进最小生成树里面的,我们只需要修改第三题kruskal算法中的返回值即可。

实验代码

#include<iostream>
#include<stdio.h>
#include<algorithm> 
using namespace std;
const int maxn = 1000000;
struct edge {//边
	int u;
	int v;
	int w;
	bool operator<(const edge& b)const { return w < b.w; }
}edge[maxn];
int par[maxn], rnk[maxn];//并查集
void init(int n) {
	for (int i = 0; i < n; i++) {
		par[i] = i;
		rnk[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 (rnk[x] > rnk[y])swap(x, y);
	par[x] = y;
	rnk[y] += rnk[x];
	return true;
}
int main() {
	int n, m, root;
	cin >> n >> m >> root;
	init(n + 1);
	for (int i = 0; i < m; i++)
		cin >> edge[i].u >> edge[i].v >> edge[i].w;
	sort(edge, edge + m);
	int ans = 0;
	int count = 0;
	for (int i = 0; i < m; i++) {
		if (unite(edge[i].u, edge[i].v) != false) {
			ans = max(ans, edge[i].w);
			count++;
		}
		if (count == n)break;
	}
	cout << ans << endl;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值