Week6作业

A题 氪金带东

题目描述

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

这里要求的每个点到其他点的最远距离,这里的最远距离只能是与直径的最远距离,问题就在于如何求得直径点,这里首先利用一次dfs求得任意一点的距离最远点v1,然后再一次dfs求得v1的最远点v2,在计算出v2的过程中我们已经得到了v1到每一点的坐标,这里还要利用一次dfs得到v2到每一点的距离,最后输出其中较大值即可

代码
#include<iostream>
#include<stdio.h>
#include<vector>
#include<string.h>
#define MAx 10000
using namespace std;
struct edge
{
    int d,w;
   edge(int i,int j):d(i),w(j){};
};
vector<edge> p[MAx+1];
int t1,t2,v1,v2,n,mdis,dis[MAx+1],dis1[MAx+1];

bool vis[MAx+1];
void dfs(int s)
{
    vis[s]=1;  
    for(int i=0;i<p[s].size();i++)
    {
        if(!vis[p[s].at(i).d])
        {
            dis[p[s].at(i).d]=dis[s]+p[s].at(i).w;
            dfs(p[s].at(i).d);
        }
    } 
}
void dfss(int s)
{
    vis[s]=1;  
    for(int i=0;i<p[s].size();i++)
    {
        if(!vis[p[s].at(i).d])
        {
            dis1[p[s].at(i).d]=dis1[s]+p[s].at(i).w;
            dfss(p[s].at(i).d);
        }
    } 
}
int find()
{
    int ma=dis[1],t=1;
    for(int i=2;i<=n;i++)
    
        if(dis[i]>ma)
            ma=dis[i],t=i;
    return t;
}
int main()
{
    while ( scanf("%d",&n)!=EOF)
    {
        for(int i=2;i<=n;i++)
        {
            scanf("%d%d",&t1,&t2); 
            p[i].push_back({t1,t2});
            p[t1].push_back({i,t2});
        }
        memset(vis,0,sizeof(vis));
        dis[1]=0;
        dfs(1);
        v1=find();
        memset(vis,0,sizeof(vis));
        dis[v1]=0;
        dfs(v1);
        v2=find();
        dis1[v2]=0;
        memset(vis,0,sizeof(vis));
        dfss(v2);
        for(int i=1;i<=n;i++)
        printf("%d\n",dis[i]>dis1[i]?dis[i]:dis1[i]);
        for(int i=1;i<=n;i++)p[i].clear();
    }
    // system("pause");
    return 0;
}
 
总结

这道题虽然是A了,但是对这个直径还是有点不理解,还需要学习啊

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-1A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
Output
输出要隔离的人数,每组数据的答案输出占一行

思路

这是一个典型的并查集问题,声明两个数组分别表示元素类别和数量。初始pre数组都是自身,sz数组都是1.
其中的归并操作merge,当两学生之前并不是一个团体时,就将i的团体代表加入j的团体,并更新j的大小,执行完所有的合并,只需要输出0所在的团体数量即可。

代码
#include<stdio.h>
#include<string.h>
using namespace std;
int pre[30002],m,n,sz[30002];
int find(int i)
{
    return i==pre[i]?i:find(pre[i]);
}
bool merge(int i,int j)
{
    i=find(i);
    j=find(j);
    if(i==j)return 0;
    pre[i]=j;
    sz[j]+=sz[i];
    return 1;
}
int main()
{
    cin>>n>>m;
    while(!(n==0&&m==0))
    {
        for(int i=0;i<n;i++)
        {
            pre[i]=i;
            sz[i]=1;
        }        
        for(int i=0;i<m;i++)
        {
            int t,t1,t2=-1;
            cin>>t;
            for(int j=0;j<t;j++)
            {
                cin>>t1;
               // cout<<t1<<"  ";
                if(t2!=-1)
                    merge(t1,t2);
                else
                    t2=t1;
            } 
        }
        cout<<sz[find(0)]<<endl;
        cin>>n>>m;
    }
   // system("pause");
    return 0;
}

总结

这道题的并查集我采用的朴素写法,好在这道题比较特殊只需要将后输入的数加入的先输入的数的团体中就能得到一个很好的并查集形态即高度不是很高。

C题 魔法东东

题目描述

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

样例

Input  
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0  
Output  
9
思路

这道题将天视为一个超级原点就可以当作最小生成树问题来决解了,利用B题中的并查集来辅助判断,将边排好序后一次选择,直到选完或者选够n-1条停止。

代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;

struct edge
{
    int s,d,w;
    bool operator <(const edge&e)const{
        return w<e.w;
    }
};
int pre[305] ;
int find(int i)
{
    return i==pre[i]?i:find(pre[i]);
}
bool merge(int i,int j)
{
    i=find(i);
    j=find(j);
    if(i==j)return 0;
    pre[i]=j; 
    return 1;
}
edge e[50000];
int n,w,cnt,c,ans;
int main()
{
    scanf("%d",&n);
    for(int i=0;i<=n;i++)
    {
        pre[i]=i;
    }
    for(int i=1;i<=n;i++)
    {
        e[cnt].d=i;
        cin>>e[cnt].w;
        e[cnt++].s=0;
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        if(i>=j)
        {
            cin>>e[cnt].w;continue;
        }
        e[cnt].s=i;
        cin>>e[cnt].w;
        e[cnt++].d=j;
    }
    sort(e,e+cnt);
    edge t;
    for(int i=0;i<cnt&&c<n;i++)
    {
        t=e[i];
        if(merge(t.s,t.d))//成功
        {
            c++;
            ans+=t.w;
        }
    }
    cout<<ans<<endl;
   // system("pause");
    return 0;
}
总结

一道典型的最小生成树问题,但是这个超级原点的思想还是学到了。

D题 数据中心

题目描述

在这里插入图片描述

样例: 
input
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2  
Output  
4
思路

这道题题干繁冗,但问题还是很简单的,就是要求最小生成树的最大边,采用Kruskal最后一次加入的边就是所求答案,这不过这里的并查集就不是朴素就能过的,得采用路径优化一下

代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
struct edge
{
    int s,d,w;
    bool operator <(const edge&e)const{
        return w<e.w;
    }
};
int pre[50005] ;
int find(int p)
{
   // return i==pre[i]?i:find(pre[i]); 
            while(p != pre[p])
            {   //如果p元素的父亲指针指向的不是自己,说明p并不是集合中的根元
                //素,还需要一直向上查找和路径压缩
                //在find查询中嵌入一个路径压缩操作
                pre[p]=pre[pre[p]];
                //p元素不再选择原来的父亲节点,而是直接选择父亲节点的父亲节点来做为自己新的一个父亲节点
                //这样的操作使得树的层数被压缩了
                p=pre[p];
                //p压缩完毕后且p并不是根节点,p变成p新的父节点继续进行查找和压缩的同时操作
            }
            return p; 
}
bool merge(int i,int j)
{
    i=find(i);
    j=find(j);
    if(i==j)return 0;
    pre[i]=j; 
    return 1;
}
edge e[100005];
int n,m,root,w,cnt,c,ans,t1,t2,t3;
int main()
{
    scanf("%d%d%d",&n,&m,&root);
    for(int i=0;i<=n;i++)
    pre[i]=i;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&e[i].s,&e[i].d,&e[i].w);
    }  
    sort(e,e+m);
    edge t;
    for(int i=0;i<m&&c<n;i++)
    {
        t=e[i];
        if(merge(t.s,t.d))//成功
        {
            c++;
            if(c==n-1)ans=t.w;
        }
    }
    cout<<ans<<endl;
   // system("pause");
    return 0;
}
总结

不要问我怎么知道要路径优化的

发布了20 篇原创文章 · 获赞 3 · 访问量 453

猜你喜欢

转载自blog.csdn.net/qq_44893580/article/details/105282448