题目一 氪金带东
题目描述
实验室里原先有一台电脑(编号为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;
}