week6作业

题目一 氪金带东

题目描述

实验室里原先有一台电脑(编号为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 (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。

example

input 1

5
1 1
2 1
3 1
1 1

output 1

3
2
3
4
4

做法与思路

这道题从某种程度上可以看做是求树的直径的变形题目。
我们之前已经学习到了求树的直径的方法,从任意一点出发,达到的最远点设为a,再从b出发,到达的最远点设为b,则ab即为树的直径。
这道题我们如果一个一个点求最长路肯定会超时,所以不妨就用使用上述的结论,最长路必然会有一端是a或者b,所以我们可以分别从a端和b端进行搜索,对到达每一点的距离进行标记,取其长者作为结果。
这样一来只需要对树三次搜索,第一次从任意一点搜索,找到a点;第二次从a点搜索,找到b点;第三次从b点搜索,更新最长距离。

代码

#include <iostream>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int h[100100];
int total=1;
bool j[100100];
int dis;
int maxx;
int p1,p2;
int di[100100];
struct edge{
	int u,v,w,next;
	edge()
	{
		u=0;v=0;w=0;next=-1;
	}
	edge(int a,int b,int c,int d)
	{
		u=a;v=b;w=c;next=d;
	}
};edge e[300100];
void adde(int u,int v,int w)
{
	e[total]=edge(u,v,w,h[u]);
	h[u]=total;
	total++;
}
void dfs1(int n)
{
	int t=h[n];
	while(t!=0)
	{	
		edge tem=e[t];
	//	j[tem.u]=1;
		if(j[tem.v]==0)
		{
			j[tem.v]=1;
			dis+=tem.w;
			if(dis>maxx)
			{
				maxx=dis;
				p1=tem.v;
			}
			dfs1(tem.v);
			j[tem.v]=0;
			dis-=tem.w;
		}
		t=tem.next;
	}
	return ;
}
void dfs2(int n)
{
	int t=h[n];
	while(t!=0)
	{	
		edge tem=e[t];
	//	j[tem.u]=1;
		if(j[tem.v]==0)
		{
			j[tem.v]=1;
			dis+=tem.w;
			di[tem.v]=dis;
			if(dis>maxx)
			{
				maxx=dis;
				p2=tem.v;
			}
			dfs2(tem.v);
			j[tem.v]=0;
			dis-=tem.w;
		}
		t=tem.next;
	}
	return ;
}
void dfs3(int n)
{
	int t=h[n];
	while(t!=0)
	{	
		edge tem=e[t];
	//	j[tem.u]=1;
		if(j[tem.v]==0)
		{
			j[tem.v]=1;
			dis+=tem.w;
			if(dis>di[tem.v])
				di[tem.v]=dis;
			dfs3(tem.v);
			j[tem.v]=0;
			dis-=tem.w;
		}
		t=tem.next;
	}
	return ;
}
int main(int argc, char** argv) {
	int n;
	while(cin>>n)
	{
		for(int i=0;i<100100;i++)
		{
			h[i]=0;
			total=1;
			di[i]=0;
		}
		
		for(int i=2;i<=n;i++)
		{
			int a,b;
			cin>>a>>b;
			adde(i,a,b);
			adde(a,i,b);
		}
		
		
		for(int i=0;i<n+30;i++)
		{
			j[i]=0;
		}
		dis=0;maxx=0;
		j[1]=1;
		dfs1(1);
		
		for(int i=0;i<n+30;i++)
		{
			j[i]=0;
		}
		dis=0;maxx=0;
		j[p1]=1;
		dfs2(p1);
	

		for(int i=0;i<n+30;i++)
		{
			j[i]=0;
		}	
		dis=0;
		j[p2]=1;
		dfs3(p2);
		
		for(int i=1;i<=n;i++)	cout<<di[i]<<endl;
	}
	return 0;
}

题目二 带好口罩

题目描述

新型冠状病毒肺炎(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

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

example

input 1

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 1

4
1
1

做法与思路

考察并查集使用的题目,目标是寻找和0号处在同一个类别下的人。
首先根据读取数据,将同一行的人视作一个类别,将第一个人看作是根节点,形成初步的并查集。
但是这样还不够,如下图所示情况
2 3 1
2 1 2
2的根节点是1,1的根节点是3,无法直接得出2的根节点是3.
这里需要一次路径压缩,遍历所有点,得到它的最终根节点。
然后再次遍历所有点,与0号根节点相同的人即为所求人群。

代码

#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
int n,m;
int father[30100];

int main(int argc, char** argv) {
	while(cin>>n>>m)
	{
		if((n==0)&&(m==0)) break;
		for(int i=0;i<n;i++)	father[i]=i;
		for(int i=0;i<m;i++)
		{
			int a,b;
			cin>>a;
			cin>>b;
			while(b[father]!=b)
			{
				b=father[b];
			}
			for(int j=1;j<a;j++)
			{
				int c;
				cin>>c;
				while(c[father]!=c)
				{
					c=father[c];
				}
				father[c]=b;
			}
		}
		for(int i=0;i<n;i++)
		{
			int x=i;
			int son=x;
			while(father[x]!=x)
			{
				x=father[x];
			}//x是最终查出来的祖先节点 
			while(father[son]!=x)
			{
				int tem=son;
				father[son]=x;
				son=tem;
			}//路径压缩 
		}
		int sum=0;
		for(int i=0;i<n;i++)
		{
			if(father[i]==father[0])
			{
				sum++;
			}
		}
		cout<<sum<<endl;
	}
	return 0;
}

题目三 掌握魔法の东东

题目描述

东东在老家农村无聊,想种田。农田有 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 1

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

output 1

9

做法与思路

如果没有“黄河之水天上来”的话这就是个很标准的求最小生成树的题目。
但是由于有黄河之水天上来,所以要额外加入一个特殊点,该点与所有点都是相邻的,且到第i个点的距离是w[i]。
然后使用kruskal算法即可得到答案。
还有值得一提的是在确定是否形成环的时候可以使用并查集。

代码

#include <iostream>
#include <algorithm>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
struct edge{
	int u,v,w;
	edge()
	{
		u=0;v=0;w=0;
	}
	edge(int a,int b,int c)
	{
		u=a;v=b;w=c;
	}
	bool operator <(const edge& tem) const
	{
		return w<tem.w;
	}
};edge e[90100];
int w;
int total;
int father[301];
int dis;
int get_father(int i)
{
	int son=i;
	while(father[i]!=i)
	{
		i=father[i];
	}
	while(father[son]!=i)
	{
		int tem=father[son];
		father[son]=i;
		son=father[son];
	}
	return i;
}
void kruskal()
{
	for(int i=0;i<total;i++)
	{
		if(get_father(e[i].u)!=get_father(e[i].v))
		{
			father[get_father(e[i].v)]=father[get_father(e[i].u)];
			dis+=e[i].w;
		}
	}
}
int main(int argc, char** argv) {
	int n; cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w;
		e[total]=edge(0,i,w);
		total++;
	}
	for(int i=0;i<=n;i++)
	{
		father[i]=i;
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		int tem;
		cin>>tem;
		if(tem!=0)
		{
			e[total]=edge(i,j,tem);
			total++;
		}
	}
	sort(e,e+total);
	kruskal();
	cout<<dis;
	return 0;
}

题目四

在这里插入图片描述

example

input 1

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

output 1

4

做法与思路

这个题目说了一大堆好像很复杂的样子,但看完之后发现就是求最小生成树,而且可以直接使用kruskal不需要做任何变形,根节点的数据都没用上。

代码

#include <iostream>
#include <algorithm>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
struct edge{
	int u,v,w;
	edge()
	{
		u=0;v=0;w=0;
	}
	edge(int a,int b,int c)
	{
		u=a;v=b;w=c;
	}
	bool operator <(const edge& tem) const
	{
		return w<tem.w;
	}
};edge e[100100];
int w;
int total,n,root;
int father[100100];
int maxx; 
int get_father(int i)
{
	int son=i;
	while(father[i]!=i)
	{
		i=father[i];
	}
	while(father[son]!=i)
	{
		int tem=father[son];
		father[son]=i;
		son=father[son];
	}
	return i;
}
void kruskal()
{
	for(int i=0;i<total;i++)
	{
		if(get_father(e[i].u)!=get_father(e[i].v))
		{
			father[get_father(e[i].v)]=father[get_father(e[i].u)];
			if(e[i].w>maxx)	maxx=e[i].w;
			n--;
			if(n==0)	return;
		}
	}
}
int main(int argc, char** argv) {
	cin>>n;
	cin>>total;
	cin>>root;
	for(int i=1;i<=n;i++)
	{
		father[i]=i;
	}
	for(int i=0;i<total;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		e[i]=edge(a,b,c);
	}
	sort(e,e+total);
	kruskal();
	cout<<maxx;
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值