程序设计作业week06

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).

Sample Input

5
1 1
2 1
3 1
1 1

Sample Output

3
2
3
4
4

解题思路

题意:每台电脑都用网线连接到一台先前安装的电脑上。希望知道第i台电脑到其他电脑的最大网线长度。

make_graph()
图的存储:使用一个结构体数组记录图中的所有边,结构体里有三个成员:u,v,w,分别表示这个边关联的两个顶点和边权。然后用邻接表存储,

find()
要找以每个顶点为端点的最长路径的长度,另一个端点一定是这个树的最长路的两个端点之一。

扫描二维码关注公众号,回复: 10576978 查看本文章

所以首先要求出整棵树的最长路端点,即求树的“直径”:
假设树的最长路的两个叶子节点为v1, v2,从任意一点 u 出发走到的最 远的点一定是(v1,v2)中的一点,然后再从 v1 或者 v2 出发走到的最 远点一定是 v2 或者 v1。由此经过两次遍历就能找到最长路径。

这个就用dfs实现,更新最大长度的同时更新端点,遍历一遍就可以标记到最长路的端点。

dfs1() dfs2()
因为另一个端点一定是这个树的最长路的两个端点之一,所以分别从v1 v2开始遍历图,分别记下到达所有顶点的长度,然后取max,就是每个顶点的最大长度。

一开始忘了上课讲的。。。就直接dfs所有顶点到v1 v2哈哈哈zz,肯定超时。。。

完整代码

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
int n;
int vis[10010];
int cnt = 0, thedis = 0, dis = 0;
int v1, v2;
int vv;

struct edge
{
	int nn;
	int len;
};
vector<edge> node[10010];
int v1_dis[100000], v2_dis[100000]; 
void make_graph(int n)
{
	for(int i=2;i<=n;i++)
	{
		int num, ll;
		scanf("%d %d", &num, &ll);
		edge e;
		e.nn = num;	e.len = ll;
		node[i].push_back(e);
		e.nn = i;
		node[num].push_back(e);
	}
}

int find(int x) 
{
	for(int i=0;i<node[x].size();i++)
	{
		int next = node[x][i].nn;
		if(vis[next] == 1)	continue;
		dis += node[x][i].len;
		if(dis > thedis)
		{
			thedis = dis;
			vv = next;
		}	
		vis[next] = 1;
		find(next);
		dis -= node[x][i].len;
	}
	return vv;
}

void dfs1(int x) 
{

	for(int i=0;i<node[x].size();i++)
	{
		int next = node[x][i].nn;
		if(vis[next] == 1)	continue;
		v1_dis[next] = v1_dis[x] + node[x][i].len;
		vis[next] = 1;
		dfs1(next);
	}
	return;
}
void dfs2(int x) 
{

	for(int i=0;i<node[x].size();i++)
	{
		int next = node[x][i].nn;
		if(vis[next] == 1)	continue;
		v2_dis[next] = v2_dis[x] + node[x][i].len;
		vis[next] = 1;
		dfs2(next);
	}
	return;
}
int main()
{
	while(scanf("%d", &n) != EOF)
	{
		make_graph(n); 
		memset(v1_dis, 0 ,sizeof(v1_dis));
		memset(v2_dis, 0 ,sizeof(v2_dis));
		for(int k=0;k<=n;k++)	vis[k] = 0;
		vis[1] = 1; thedis = 0;  vv = -1;
		v1 = find(1);
		
		for(int k=0;k<=n;k++)	vis[k] = 0;
		vis[v1] = 1; thedis = 0;  vv = -1;
		v2 = find(v1);

		for(int k=0;k<=n;k++)	vis[k] = 0;
		vis[v1] = 1; 
		dfs1(v1);
		
		for(int k=0;k<=n;k++)	vis[k] = 0;
		vis[v2] = 1; 
		dfs2(v2);

		for(int i=1;i<=n;i++)
			cout<<max(v1_dis[i], v2_dis[i])<<endl;

		for (int i=0;i<=n;i++)
        	node[i].clear(); 
	}
	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

输出要隔离的人数,每组数据的答案输出占一行

Sample 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

Sample Output

4
1
1

解题思路

这是要一个典型的分组分类问题,当然是用并查集解决。

并查集的实现:

初始化
指示 n 个孤立的元素,它们独自是一个分组
所以初始化即标识代表元是它们自己本身
查询操作
我们要查询一个节点所在的集合的编号(代表元的编号)
一般来说,对于树形的结构,我们默认代表元是根
因为它是所有节点的祖先(父亲的父亲的父亲的…父亲)
我们可以顺着边往上找 -> 找代表元的方法统一 -> 可以封装成一个方法
路径压缩
为了提高查询的复杂度,丢弃节点间的关系
—— 把自己直接挂在根节点下
合并
把一个分组的根挂到另一个分组的根即可

完整代码

#include <iostream>
#include <stdio.h>
#include <string.h>
int n, m;
int f[30010], cnt[30010];
using namespace std;

int find(int x)
{
	if(x == f[x])	return x;
	find(f[x]);
}

void Union(int x,int y)
{
    int fx, fy;
    fx = find(x);	fy = find(y);
    f[fx] = fy;
    if(fx == fy)  return;
    cnt[fy] += cnt[fx];
}

int main()
{
	int ans;
    while(true)
    {
        cin>>n>>m;
        if(n==0 && m==0)  break;
        for(int i=0;i<n;i++)
		{
			f[i] = i;
			cnt[i] = 1;
		}   
        for(int i=0;i<m;i++)
        {
            int k;
            cin>>k;
            int num, before = -1;
            for(int j=0;j<k;j++)
            {
                cin>>num;
                if(j > 0)    Union(before, num);
                before = num;
            }
        }
        ans = cnt[find(0)];
        cout<<ans<<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值

Example
Input

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

Output

9

解题思路

黄河之水天上来,那就加一个顶点,把“天”记0号顶点。对着n+1个顶点求最小生成树。

kruskal算法
将全部边按照权值由小到大排序。

sort(e, e+k, cmp);

按顺序(边权由小到大的顺序)考虑每条边,只要这条边和我们已经选 择的边不构成圈,就保留这条边,否则放弃这条边。啊这里判断是否成环用到了并查集。

if(find(e[i].u) != find(e[i].v))
		{
			Union(e[i].u, e[i].v);
			ans = ans + e[i].w;
		}

成功选择(n-1)条边后,形成一棵最小生成树,当然如果算法无法选择出(n-1)条边,则说明原图不连通。

完整代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int id[310];
int ans = 0;

struct edge
{
	int u, v, w;
}e[100000];
 
bool cmp(edge x,edge y)
{
	return x.w<y.w;
}
 
int find(int x)
{
	if(id[x] == x)	return x;
	return id[x]=find(id[x]);
}

void Union(int x,int y)
{
	int a = find(x);
	int b = find(y);
	id[a] = b;
}
 
int main()
{
    cin>>n;
    int k = 0;
    for(int i=1;i<=n;i++)
    {
    	int ww;	cin>>ww;
    	e[k].u = 0;	e[k].v = i;
    	e[k].w = ww;
    	k++;
	}
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
    	int ww;	cin>>ww;
    	if(i == j)	continue;
    	e[k].u = i;	e[k].v = j;
    	e[k].w = ww;
    	k++;
	}	    
	sort(e, e+k, cmp);
	
	for(int i=0;i<=n;i++)	id[i]=i;
	for(int i=0;i<k;i++)
	{
		if(find(e[i].u) != find(e[i].v))
		{
			Union(e[i].u, e[i].v);
			ans = ans + e[i].w;
		}
	}
	cout<<ans<<endl; 
	return 0;
}

D - 数据中心

题目

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述Example

Input

4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2

Output

4

解题思路

压缩一下题意,就是一个最小瓶颈生成树问题。
性质:无向图的最小生成树一定是最小瓶颈生成树。
这篇博客有证明:
https://www.cnblogs.com/c1299401227/p/5808380.html

所以问题转换成了求最小生成树。这题同样用kruskal求最小生成树,先给所有边按边权从小到大排个序,一路更新ans,这样最后一条边就是记的边权最大的边。

完整代码

#include <iostream>
#include <algorithm>
using namespace std;
int n, m, root;
int num = 0, ans = 0;
int f[100000];
struct edge
{
    int u, v, w;
}e[100000];
bool cmp(edge x, edge y)
{
    return x.w < y.w;
}
int find(int x)
{
    return f[x] == x ? x : f[x] = find(f[x]);
}
bool Union(int x, int y)
{
    x = find(x);	y = find(y);
    if(x == y)	return false;
    f[x] = y;
    return true;
}
int main()
{
    cin>>n>>m>>root;
    for(int i=0;i<n;i++)	f[i] = i;
    for(int i=0;i<m;i++)	cin>>e[i].u>>e[i].v>>e[i].w;
    sort(e, e+m, cmp);
    
    for(int i=0;i<m;i++)
	{
        if(Union(e[i].u, e[i].v))
		{
            ans = e[i].w;
            num += 1;
            if(num == n-1)	break;
        }
    }
    cout<< ans<< endl;
    return 0;
}

总结:
这次作业学到了很多新的算法,比如求树的直径,还有求最长路。并查集的应用非常广泛,写起来也很方便。然后就是求最小生成树,两次都用了kruskal。特别是最后一题最小瓶颈生成树的性质,受益匪浅hhhh。

发布了10 篇原创文章 · 获赞 3 · 访问量 753

猜你喜欢

转载自blog.csdn.net/wakeupshely/article/details/105205489