程序设计思维与实践 Week6 作业 (3/4/数据班)

氪金带东

实验室里原先有一台电脑(编号为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

问题分析

这是一个求直径的问题的衍生

首先找出两个端点,用的方法是,任意选取一点记录下到它距离最远的点,必定是直径的一个端点。从一个端点出发,再找距离最远的点,就是另一个端点。

然后转化为求某点到端点的路径的最大值。

#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e4+5;
struct edge{
	int p,w;
	edge(int _p, int _w):p(_p), w(_w){
	}
};
vector<edge> points[N];
int path[N];
int a, b, s;
int maxl;
void DFS(int u, int v, int l){
	int next,w;
	if(l > maxl){
		s=u;
		maxl=l;
	}
	for(int i=0; i<points[u].size(); ++i)
	{
		next=points[u][i].p;
		w=points[u][i].w;
		if(next==v)continue;
		int new_l=w + l;
		DFS(next, u, new_l);
		path[next]=max(path[next], new_l);
	} 
}
int main(){
	int n;
	while(cin>>n){
		maxl=0;
		for(int i=1;i<=n;i++){
			points[i].clear();
		}
		for(int i=2;i<=n;i++){
			cin>>a>>b;
        	points[i].push_back(edge(a,b));
        	points[a].push_back(edge(i,b));
		}
		memset(path,0,sizeof(path));
		DFS(1,0,0);
		DFS(s,0,0);
		DFS(s,0,0);
		for(int i=1;i<=n;i++){
			cout<<path[i]<<endl;
		}
	}
	return 0;
}

戴好口罩!

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

Input

多组数据,对于每组测试数据:
第一行为两个整数nmn = 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

问题分析

由于数量庞大,使用模拟链表的数组储存。

并查集,每次查找的时候,使用路径压缩的思想,把当前查询节点的父节点直接变为根节点。

最后输出答案,需要统计和0号病人接触的人的数目,需要先查找一次,确保同一个群体的不会出现多种父节点的值,之后把和0号病人父节点一样的节点的数目统计出即可。

#include<iostream>
#include<cstdio>

using namespace std;

const int N=3e4+5;


int parent[N];

int find(int x)
{
	if(x!=parent[x]){
		int m=find(parent[x]);
		parent[x]=m;
		return m;
	}
	return x;
}

int Union(int a, int b){
	int _a=find(a);
	int _b=find(b);
	if(_a!=_b){
		parent[_a]=_b;
	}
}

int main()
{
	int m,n;
	while((~scanf("%d%d",&n,&m))&&!(m==0&&n==0))
	{
		for(int i=0;i<n;++i)
		{
			parent[i]=i;
		}
		for(int i=0;i<m;++i)
		{
			int t;
			scanf("%d",&t);
			int* temp =new int[t];
			for(int k=0;k<t;++k){
				scanf("%d",&temp[k]);
				if(k>0){
					Union(temp[k],temp[0]);
				}
			}
			delete[]temp;
		}
		int zero=find(0);
		int sum=1;
		for(int i=1;i<n;++i)
		{
			if(find(i)==zero){
				sum++;
			}
		}
		printf("%d\n",sum);
	}
	return 0;
 } 

掌握魔法の东东 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,水源到其他节点的路径都以及其他节点之间的路径距离已知,只需要用prim求出最小连通图。

有一点需要注意的是, 设最大值的时候,最好设置成不超过0x3fffffff,填充的时候使用fill(),否则可能会出错。

#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
const int maxv=1e5+5;
const int maxn=0x7fffffff;
struct Node{
	int v,dis;
	Node(int _v, int _dis):v(_v),dis(_dis){
	}
	Node():v(0),dis(0){
	}
};
vector<Node> Adj[maxv];
int n;
int d[maxv];
bool vis[maxv]={false};
int prim(){
	fill(d,d+maxv,maxn);
	d[0]=0;
	int ans=0;
	for(int i=0;i<n;++i){
		int u=-1,MIN=maxn;
		for(int j=0;j<n;++j){
			if(vis[j]==false&&d[j]<MIN){
				u=j;
				MIN=d[j];
			}
		}
		if(u==-1) return -1;
		vis[u]=true;
		ans+=d[u];
		for(int j=0;j<Adj[u].size();++j){
			int v=Adj[u][j].v;
			if(vis[v]==false&&Adj[u][j].dis<d[v]){
				d[v]=Adj[u][j].dis;
			}
		}
	}
	return ans;
}

int main()
{
	scanf("%d",&n);
	n++;
	int t;
	for(int i=1;i<n;++i){
		scanf("%d",&t);
		Adj[0].push_back(Node(i,t));
		Adj[i].push_back(Node(0,t));
	}
	for(int i=1;i<n;++i){
		for(int j=1;j<n;++j)
		{
			scanf("%d",&t);
			if(t==0){
				continue;
			}
			Adj[i].push_back(Node(j,t));
		}
	}
	printf("%d\n",prim());
	return 0;
}

数据中心

原题见 http://118.190.20.162/view.page?gpid=T83

问题分析

题目很长,但还是一个最小生成树的问题

用到了克鲁斯卡尔算法,本质还是求最小生成树,然后输出所选边的最大边长。

克鲁斯卡尔的思路,是确定一个点为开始点,每次加入一个到这个点最短的点,并更新其他点到它的距离。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n, m, i, j, u, v, total;
struct Edge{
    int start, to;
    int val;
    operator < (const Edge& edge){
    	return val<edge.val;
	}
};
Edge edge[500005];
int pre[100005];
 
int find(int x){
    if(pre[x] == x){
        return x;
    }else{
        pre[x]=find(pre[x]);
        return pre[x];
    }   
}
int kruskal(){
	int ans=0;
    for(int i = 1; i <= m; i++){
        u=find(edge[i].start);
        v=find(edge[i].to);
        if(u == v) continue;
        ans = ans>edge[i].val?ans:edge[i].val;
        pre[u] = v;
        total++;
        if(total == n - 1) break;
    }
    return ans;
}
int main(){
    cin >> n >> m;
    int root;
    cin >> root;
    for(i = 1; i <= n; i++) pre[i] = i;
    for(i = 1; i <= m; i++){
        cin >> edge[i].start >> edge[i].to >> edge[i].val;
    }
    sort(edge + 1, edge + m + 1);
    cout<<kruskal()<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/master-cn/p/12625794.html