A - 氪金带东
问题描述
实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
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
解题思路
- 树的直径即为树中最远节点的距离
- 树中任意一点到其他节点的最远距离即为该点到直径两端点之一的距离
- 所以求某节点到其他节点的最大最大距离,需先求出树直径的端点,再求该点到两端点的距离,取最大。
- 求直径:任取一点,dfs,记录到达所有叶节点的长度和点(每往下一层,长度等于原长度+当前边长度),长度最大值的点即为一个端点;再从该端点遍历,找到的点为另一个端点。
- 从两个端点分别往另一侧遍历,记录到达每个点的距离,取较大的输出即可(本题使得每个点只能到达一次)。
- 用 Node结构体存储点和边长度,用vector <vector< Node > > e二维数组存边,e[i][j]表示与i节点相邻的第j个节点的信息(值、边权)
完整代码
//先找直径的端点
//从两端点遍历要到达的点,得到最远路径
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
int v1,v2,v;//直径的端点
int N;
int Max=0;
int llength[10001];
struct Node
{
int n;
int length;
};
vector <vector< Node > > e;//二维数组,e[i][j]表示与i节点 相邻的第j个节点
//找从a开始最大路的点
void findv(int a,int b,int Length=0)
{
if(e[a].size()==1)
if(Length>Max) v = a,Max = Length;
for(int i = 0;i<e[a].size();i++)
{
if(e[a][i].n!=b)
findv(e[a][i].n,a,e[a][i].length+Length);
}
}
void maxlength(int a,int be,int Length=0)
{
if(Length>llength[a])
{llength[a]=Length;}
for(int i=0;i<e[a].size();i++)
if(e[a][i].n!=be)
{
maxlength(e[a][i].n,a,Length+e[a][i].length);
}
}
int main()
{
while(scanf("%d",&N)!=-1)
{ memset(llength,0,sizeof(llength));
e.clear();
e.resize(N+10);
for(int i=2;i<=N;i++)
{
int a,b;
scanf("%d %d",&a,&b);
Node nod;
nod.n=a;
nod.length=b;
e[i].push_back(nod);
Node nodd;
nodd.n = i;
nodd.length = b;
e[a].push_back(nodd);
}
Max=0;
findv(1,0);
v1=v;
Max=0;
findv(v1,0);
v2=v;
maxlength(v1,0);
maxlength(v2,0);
for(int l=1;l<=N;l++)
printf("%d\n",llength[l]);
}
}
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
Sample Output
4
1
1
解题思路
本题为简单的并查集问题
- 用parent数组表示每个节点的祖先,起初每个节点的祖先都是自己,即parent[i]=i
- 对每个集合,每输入一个元素合并一个,如果有已知集合的元素 ,则会根据parent的值合并
- a与b合并时,先找到a、b最“往上”的祖先,判断是否相等。是则不合并,不是则把其中一个的parent置为另一个,另一个祖先下节点的数目=自身数目+新合并节点祖先下的数目
- 找到0的祖先,输出它的节点数目即可
完整代码
#include<iostream>
using namespace std;
int n,m,num;
int parent[30010],number[30010];
void unite(int a,int b)
{//找最原始的parent
while(parent[a]!=a)
a=parent[a];
while(parent[b]!=b)
b=parent[b];
if(a!=b)//不属于一个则合并
{ //b的parent改为a
parent[b]=a;
number[a]=number[a]+number[b];
}
}
int main()
{
while(cin>>n>>m)
{
for(int i=0;i<n;i++)
parent[i]=i,number[i]=1;//起初每个人的代表都是自己,个数都是1个
if(n==0&&m==0)
break;
for(int i=0;i<m;i++)
{ cin>>num;
int element1,element2;
for(int l=0;l<num;l++)
{ cin>>element2;
if(l==0)
{element1=element2;continue;}
unite(element1,element2);
element1=element2;
} }
int x=0;
while(parent[x]!=x)
x=parent[x];
cout<<number[x]<<endl;
}
}
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
解题思路
- 本题可转化为最小生成树问题,每次选的方式都是增加一块被灌溉的田,所以每次选消耗最小的方式即可。
- 类似Kruskal算法,先将所有黄河之水天上来的消耗排序,每次取最小的,判断一下对应的田是否已被灌溉,再找从已被灌溉的田中使用传送门消耗mp的最小值,选较小的实施,直到所有田都被灌溉。
- 用reach数组表示田的灌溉情况,结构体、二维矩阵分别储存两种灌溉方法消耗的mp。
完整代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
struct W
{
int number;
int weight;
};
W w[302];
int reach[302];
W node[302];
int p[302][302];
int count1=0;
int count2=0;
int mp=0;
void find()//从黄河之水天上来和传送门方法里找最小的
{ while(count2<n)
{ int a=-1;
int b=-1;
while(reach[w[count1].number]==1)
count1++;
int min=w[count1].weight;//首选黄河之水天上来
// cout<<"weight"<<w[count1].weight<<" i "<<w[count1].number<<endl;
for(int i=0;i<count2;i++)//看所有的已知点有没有传送门耗蓝更少
for(int l=0;l<n;l++)
if(p[node[i].number][l]<min)
{
if(reach[l]==0)//加入不会成环
{ a=node[i].number;b=l;
min=p[a][b];
} }
if(a!=-1)//传送门
{node[count2].number=b;
count2++;
reach[b]=1;
}
else
{
node[count2].number=w[count1].number;
reach[w[count1].number]=1;
count1++;
count2++;
}
// cout<<"a"<<a<<" "<<"b"<<b<<endl;
mp=mp+min;}
}
bool compare(W a,W b)
{
return a.weight<b.weight;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{ w[i].number=i;cin>>w[i].weight;}
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>p[i][j];
memset(reach,0,sizeof(reach));
sort(w,w+n,compare);
find();
cout<<mp<<endl;
}
D - 数据中心
Example
Input
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
Output
4
解题思路
- 要找的T(max)即为每层的Tm的max,而每层的Tm即为每层边权最大的边,即T(max)=最大边。
- 本题可简化为使最大边最小,即找最小生成树即可。
- 采用kruskal算法构建最小生成树,用并查集保证不成环。
- 将所有的边按权值排序,每次取最小边,将两个节点合并(判断祖先是否一致,是则加入会成环,不合并,跳过。否则合并),记录最大值,最后输出即可。
- 结构体存边(u,v,w),parent数组表示每个点的祖先(初始化parent[i]=i)
完整代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct Edge
{
int begin;
int end;
int length;
};
Edge e[100010];
int parent[50010];
int n,m;
bool compare(Edge a,Edge b){return a.length<b.length;}
int find(int x)
{ return parent[x]==x?x:parent[x]=find(parent[x]);
}
bool unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y)
return false;
parent[x]=y;
return true;
}
int mintree()
{ sort(e,e+m,compare);
int Max=0;
for(int i=0;i<m;i++)
{ if(unite(e[i].begin,e[i].end))//两个点都到过了
if(e[i].length>Max)
Max=e[i].length;
} return Max;}
int main()
{
cin>>n>>m;
int root;
cin>>root;
for(int i=0;i<m;i++)
cin>>e[i].begin>>e[i].end>>e[i].length;
for(int i=0;i<n;i++)
parent[i]=i;
cout<<mintree()<<endl;
}