氪金带东
实验室里原先有一台电脑(编号为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
多组数据,对于每组测试数据:
第一行为两个整数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 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;
}
数据中心
问题分析
题目很长,但还是一个最小生成树的问题
用到了克鲁斯卡尔算法,本质还是求最小生成树,然后输出所选边的最大边长。
克鲁斯卡尔的思路,是确定一个点为开始点,每次加入一个到这个点最短的点,并更新其他点到它的距离。
#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;
}