week6——作业(图和树)

氪金带东 :

问题描述

题目简述

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

输入/输出格式

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

样例

输入样例:
5
1 1
2 1
3 1
1 1
输出样例:
3
2
3
4
4

问题分析

解题思路

这个题需要使用树的直径的相关知识。树的直径是树中最长的一条路径。树中的每一个点的最长路径的终点必然是树的直径的两端的其中一个。因此,要求出每一个点的最长路径,需要先求出树的直径的两个端点,之后求出每一个点到这两个端点的距离,取最大值即为结果。在本题中,首先对任一个点进行bfs(或dfs)搜索,之后,求出距离最长的点,即为直径的一个端点。之后对该点再进行bfs(或dfs),找到距离最大的点,即为第二个端点。最后对第二个端点进行一次bfs(或dfs),输出最大值即可。

参考代码
#include <iostream>
#include <vector>
#include <cstdio>
#include <queue>
#include <cstring>

using namespace std;

class edge
{
public:
	int length;
	int to;
};

class point
{
public:
	int tag;
	vector<edge> v;
};

int main()
{
	int dis[10010];
	int dis2[10010];
    int find[10010];
	int n;
    while(scanf("%d",&n)!=EOF)
    {
    	queue<int> q;
    	memset(dis,0,sizeof(dis));
    	memset(dis2,0,sizeof(dis2));
    	memset(find,0,sizeof(find));
    	point *gra=new point[n+10];
    	int a,b;
    	for(int i=2;i<=n;i++)
    	{
    		scanf("%d %d",&a,&b);
    		gra[a].v.push_back({b,i});
    		gra[i].v.push_back({b,a});
		}
		q.push(1);
		find[1]=1;
		while(!q.empty())
		{
			int temp;
			temp=q.front();
			for(int i=0;i<gra[temp].v.size();i++)
			{
				if(find[gra[temp].v[i].to]==0)
				{
					//printf("dis[%d]=dis[temp]+gra[temp].v[i].length:%d+%d\n",gra[temp].v[i].to,dis[temp],gra[temp].v[i].length);
					dis[gra[temp].v[i].to]=dis[temp]+gra[temp].v[i].length;
				    find[gra[temp].v[i].to]=1;
				    q.push(gra[temp].v[i].to);
				}
			}
			q.pop();
		}
		int max=dis[1];
		int d1=1;
		int d2=1;
		for(int i=1;i<=n;i++)
		{
			if(max<dis[i]) 
			{
				d1=i;
			    max=dis[i];
			}
		}
		//printf("d1=%d\n",d1);
		memset(dis,0,sizeof(dis));
    	memset(find,0,sizeof(find));
    	q.push(d1);
    	find[d1]=1;
    	while(!q.empty())
		{
			int temp;
			temp=q.front();
			for(int i=0;i<gra[temp].v.size();i++)
			{
				if(find[gra[temp].v[i].to]==0)
				{
					//printf("dis[%d]=dis[temp]+gra[temp].v[i].length:%d+%d\n",gra[temp].v[i].to,dis[temp],gra[temp].v[i].length);
					dis[gra[temp].v[i].to]=dis[temp]+gra[temp].v[i].length;
				    find[gra[temp].v[i].to]=1;
				    q.push(gra[temp].v[i].to);
				}
			}
			q.pop();
		}
		max=dis[d1];
		for(int i=1;i<=n;i++)
		{
			if(max<dis[i]) 
			{
				d2=i;
			    max=dis[i];
			}
		}
		//printf("d2=%d\n",d2);
		memset(find,0,sizeof(find));
		q.push(d2);
		find[d2]=1;
		while(!q.empty())
		{
			int temp;
			temp=q.front();
			for(int i=0;i<gra[temp].v.size();i++)
			{
				if(find[gra[temp].v[i].to]==0)
				{
					//printf("dis2[%d]=dis2[temp]+gra[temp].v[i].length:%d+%d\n",gra[temp].v[i].to,dis2[temp],gra[temp].v[i].length);
				    dis2[gra[temp].v[i].to]=dis2[temp]+gra[temp].v[i].length;
				    find[gra[temp].v[i].to]=1;
				    q.push(gra[temp].v[i].to);
				}
			}
			q.pop();
		}
		for(int i=1;i<=n;i++)
		{
			if(dis[i]>=dis2[i]) printf("%d\n",dis[i]);
			else printf("%d\n",dis2[i]);
		}
	}
	
	return 0;
}

心得体会

这个题本身不难,但是WA了3次。。。原因是在bfs的时候没有先标记搜索起点,导致搜索错误。可见对bfs的掌握还不是很熟悉。以后还需要对算法更加的落实。

戴好口罩! :

问题描述

题目简述

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

输入/输出格式

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

样例

输入样例:
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
输出样例:
4
1
1

问题分析

解题思路

经典的并查集问题。利用并查集,维护一个代表并查集个数的数组,当两个不同的并查集合并的时候,相对应的也将被合并集的数量加到合并集数组上即可。最后搜索0号同学所在的并查集个数,即可解决。

扫描二维码关注公众号,回复: 10191553 查看本文章
参考代码
#include <cstdio>

using namespace std;

int main()
{
	int n,m;
	scanf("%d %d",&n,&m);
	while(n!=0||m!=0)
	{
		int *students=new int [n];
		int *rank=new int [n];
		for(int i=0;i<n;i++)
		{
			students[i]=i;
			rank[i]=1;
		}
		for(int j=1;j<=m;j++)
		{
			int mem_num;
			scanf("%d",&mem_num);
			int a,b;
		    if(mem_num>0) scanf("%d",&a);
		    else continue;
		    int tmp=a;
		    while(students[tmp]!=tmp)
		    {
		    	tmp=students[tmp];
			}
		    for(int k=2;k<=mem_num;k++)
		    {
		    	scanf("%d",&b);
		    	int temp=b;
		    	while(students[temp]!=temp)
		    	{
		    		temp=students[temp];
				}
				if(tmp!=temp)
				{
					rank[tmp]+=rank[temp];
				    students[temp]=tmp;
				}
			}
		}
		int t=0;
		while(t!=students[t])
		{
			t=students[t];
		}
		printf("%d\n",rank[t]);
		scanf("%d %d",&n,&m);
	}
	return 0;
}

心得体会

WA了两次,最后合并的时候忘记判断是否属于不同并查集,导致了相同并查集也被合并了。虽然并查集已经比较熟悉了,但是,没写过,没用过还是不行。代码写的还是少,思路还是不清晰。

掌握魔法の东东 :

问题描述

题目简述

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

输入/输出格式

输入格式:
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
输出格式:
东东最小消耗的MP值

样例

输入样例:
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
输出样例:
9

问题分析

解题思路

种田大法好!(划掉)这个是一个比较有意思的题目,假如东东没有学习黄河之水天上来这个技能的话,那么,以这n块农田为结点,以在任意两块农田间施放传送面板已上线,我们的行动会…啊啊啊不对,是传送门的消耗为边,那么,这个问题就变成了一个单纯的kruskal算法问题。然而,东东学会了黄河之水这个技能,导致不能这么做。因此,要用一个方法统一一下这个图。为此,我们新加入一个结点,这个结点到其他农田结点的路径是在该农田上施放黄河之水的消耗。这样,就可以发现这两种消耗被统一成了一种。这样,将问题转化为了n+1个结点的图的kruskal问题。即可解决。

参考代码
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;

class edge
{
public:
	int from;
	int to;
	int mp;
	bool operator <(const edge& e) const
	{
		return mp<e.mp;
	};
};

int main()
{
	int n;
	scanf("%d",&n);
	int *points=new int [n+10];
	for(int i=0;i<=n;i++)
	{
		points[i]=i;
	}
	vector<edge> v;
	for(int i=1;i<=n;i++)
	{
		int m;
		scanf("%d",&m);
		v.push_back({0,i,m});
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			int m;
			scanf("%d",&m);
			if(m==0||j>i) continue;
			v.push_back({i,j,m});
		}
	}
	sort(v.begin(),v.end());
	int count=0;
	long long ans=0;
	while(count!=n)
	{
		edge t=v[0];
		int a,b;
		a=t.from;
		b=t.to;
		//printf("search edge:{%d,%d,%d}\n",t.from,t.to,t.mp);
		int tmp=a;
		while(points[tmp]!=tmp)
		{
		    tmp=points[tmp];
		}
		int temp=b;
		while(points[temp]!=temp)
		{
		    temp=points[temp];
		}
		if(temp!=tmp)
		{
			count++;
			ans+=t.mp;
			points[temp]=tmp;
		}
		v.erase(v.begin());
	}
	printf("%lld\n",ans);
	return 0;
}

心得体会

之前上课的时候也讲过加入结点的思想,但是,我感觉没怎么搞明白,通过这个题,我恍然大悟,明白了这个解题思路,这的确是一个巧妙的简化问题的好方法。

数据中心(CSP 201812 04):

问题描述

题目简述

copy the question here.

样例

输入样例:
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
输出样例:
4
在这里插入图片描述

问题分析

解题思路1(70分)

这个其实也很接近满分了。但是还是差了一点点。思路是求最小生成树的最长边。因为,最小生成树是连接图中所有点的最低消耗的路径,而最小生成树的最长边,则一定可以满足题目要求。(其它合法的数一定比它大,否则一定不能通过最小生成树)这样,使用kruskal算法就可以解决。

参考代码1

在这里插入图片描述

#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;

class edge
{
public:
	int from;
	int to;
	int length;
	bool operator <(const edge& e) const
	{
		return length<e.length;
	};
};

int main()
{
	int n,m,root;
	scanf("%d %d %d",&n,&m,&root);
	int *points=new int [n+10];
	for(int i=0;i<=n;i++)
	{
		points[i]=i;
	}
	vector<edge> v;
	for(int i=1;i<=m;i++)
	{
		int from,to,length;
		scanf("%d %d %d",&from,&to,&length);
		v.push_back({from,to,length});
	}
	sort(v.begin(),v.end());
	int count=0;
	int ans=0;
	int k=0;
	while(count!=n-1)
	{
		edge t=v[k];
		int a,b;
		a=t.from;
		b=t.to;
		//printf("search edge:{%d,%d,%d}\n",t.from,t.to,t.length);
		int tmp=a;
		while(points[tmp]!=tmp)
		{
		    tmp=points[tmp];
		}
		int temp=b;
		while(points[temp]!=temp)
		{
		    temp=points[temp];
		}
		if(temp!=tmp)
		{
			count++;
			if(t.length>ans) ans=t.length;
			points[temp]=tmp;
		}
		k++;
	}
	printf("%d\n",ans);
	return 0;
}

然而,最后一组运行超时。这个方法是有缺陷的。

解题思路2(100分)

其实就差了一点点,想一下,在并查集合并的时候,这个并查集可能变成一条链式的结构。一旦如此,从链的尾部找代表元素的时候,耗时就会很长。如果这个过程重复若干次,那么,浪费的时间就很长,就会超时。因此,需要使用路径压缩的方法,当发现当前的点存的不为代表元素的位置时,就把它的值置为代表元素。这样,就可以避免重复浪费时间的搜索。

参考代码2

在这里插入图片描述

#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
#include <stack>

using namespace std;

class edge
{
public:
	int from;
	int to;
	int length;
	bool operator <(const edge& e) const
	{
		return length<e.length;
	};
};

int main()
{
	int n,m,root;
	scanf("%d %d %d",&n,&m,&root);
	int *points=new int [n+10];
	for(int i=0;i<=n;i++)
	{
		points[i]=i;
	}
	vector<edge> v;
	for(int i=1;i<=m;i++)
	{
		int from,to,length;
		scanf("%d %d %d",&from,&to,&length);
		v.push_back({from,to,length});
	}
	sort(v.begin(),v.end());
	int count=0;
	int ans=0;
	int k=0;
	while(count!=n-1)
	{
		edge t=v[k];
		int a,b;
		a=t.from;
		b=t.to;
		//printf("search edge:{%d,%d,%d}\n",t.from,t.to,t.length);
		stack<int> s1;
		stack<int> s2;
		int tmp=a;
		while(points[tmp]!=tmp)
		{
			s1.push(tmp);
		    tmp=points[tmp];
		}
		while(!s1.empty())
		{
			points[s1.top()]=tmp;
			s1.pop();
		}
		int temp=b;
		while(points[temp]!=temp)
		{
			s2.push(temp);
		    temp=points[temp];
		}
		while(!s2.empty())
		{
			points[s2.top()]=temp;
			s2.pop();
		}
		if(temp!=tmp)
		{
			count++;
			if(t.length>ans) ans=t.length;
			points[temp]=tmp;
		}
		k++;
	}
	printf("%d\n",ans);
	return 0;
}

心得体会

这道题说实话,不像看上去那么难。题目非常的难读,但是读懂之后就会发现,没有想象中的那么难。另外,路径压缩还是要掌握的,并查集合并的时候会有很极端的情况。使用路径压缩很能节省时间。以后使用并查集的时候要注意。最后一点:以后写递归!!!模拟递归看上去好笨啊!!!

发布了10 篇原创文章 · 获赞 1 · 访问量 169

猜你喜欢

转载自blog.csdn.net/qq_43715114/article/details/105117274
今日推荐