SDU程序设计思维与实践作业Week5
A-氪金带东
题目
Input&&Output:
Sample:
#input:
5
1 1
2 1
3 1
1 1
#output:
3
2
3
4
4
题解
1.本题要求任意点可以到达的最长距离
2.分析可得首先本题的图是一个连通图,而且根据边数可得没有环
3.再者要求点出发到达的最远距离,我们不妨这样想,点出发到达的最远距离实际上
也是最远距离出发到他的距离
4.显然从直径的某一个端点出发(并进行dfs)到该点可以得到它的最远距离
5.直径两个端点,因此我们只要选出较大的就好了
6.同样我们从任意一个点出发dfs得到的最远点就是直径的某一个端点,而从直径dfs
的最远位置就是直径的另一个端点(因此本题3次dfs)
C++代码
//代码提供两种 第一种存边使用邻接表 dfs使用循环 另一种使用链式前向星 dfs
//采用递归 ;邻接矩阵过于占用内存,对于本题会爆内存(可能我写的不够好233
//1.邻接表
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1e4+500;
int mark[maxn],MAX[maxn],MAX2[maxn],N;
vector<int> w[maxn],e[maxn];
queue<int> q;
void init(int m){
for(int i=0;i<m;i++){
mark[i] = 0;
MAX[i] = 0;
MAX2[i] = 0;
w[i] = vector<int>(0);
e[i] = vector<int>(0);
}
}
int dfs(int x){
q.push(x);
int m=0,mid=0;
MAX[x] = 0;
while(!q.empty()){
x = q.front();q.pop();
if(mark[x]==0){
mark[x] = 1;
for(int i=0;i<e[x].size();i++){
if(mark[e[x][i]]==0){
q.push(e[x][i]);
if(MAX[x]+w[x][i]>MAX[e[x][i]]) MAX[e[x][i]] = MAX[x]+w[x][i];
}
}
}
}
for(int i = 0;i<N;i++){
if(MAX[i]>mid){
m=i;
mid=MAX[i];
}
mark[i] = 0;
}
return m;
}
int main(){
int a,b,m;
while(cin>>N){
init(N);
for(int i=0;i<N-1;i++){
cin>>a>>b;
e[a-1].push_back(i+1);
w[a-1].push_back(b);
e[i+1].push_back(a-1);
w[i+1].push_back(b);
}
m = dfs(0);
for(int i=0;i<N;i++) {MAX[i]=0;}
m = dfs(m);
for(int i=0;i<N;i++){MAX2[i] = MAX[i];MAX[i] = 0;}
dfs(m);
for(int i=0;i<N;i++) cout<<max(MAX[i],MAX2[i])<<endl;
}
return 0;
}
//2.链式前向星
#include<iostream>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn=1e4+500;
int mark[maxn],MAX[maxn],MAX2[maxn],N,tot=0,head[maxn],vm;
struct Edge
{
int u,v,w,next;
}Edges[2*maxn];
void addEdge(int u,int v,int w)
{
Edges[tot].u=u;
Edges[tot].v=v;
Edges[tot].w=w;
Edges[tot].next=head[u];
head[u]=tot;
tot++;
}
void init(int m){
tot=0;vm=0;
memset(head,-1,sizeof(head));
for(int i=0;i<m;i++){
mark[i] = 0;
MAX[i] = 0;
MAX2[i] = 0;
}
}
void dfs(int x){
mark[x] = 1;
for(int i=head[x];i!=-1;i=Edges[i].next){
if(mark[Edges[i].v]==0){
MAX[Edges[i].v] = MAX[x]+Edges[i].w;
dfs(Edges[i].v);
}
}
}
int main(){
int a,b,m,mid;
while(cin>>N){
init(N);
for(int i=0;i<N-1;i++)
{
cin>>a>>b;
addEdge(a-1,i+1,b);
addEdge(i+1,a-1,b);
}
dfs(0);mid=0;//重置标记矩阵 max矩阵 vm:最远端点
for(int i=0;i<N;i++) { if(mid<MAX[i]) {vm=i;mid=MAX[i];} MAX[i]=0;mark[i]=0;}
dfs(vm);mid=0;
vm=0;
for(int i=0;i<N;i++){if(mid<MAX[i]) {vm=i;mid=MAX[i];} MAX2[i] = MAX[i];MAX[i] = 0;mark[i]=0;}
dfs(vm);
for(int i=0;i<N;i++) cout<<max(MAX[i],MAX2[i])<<endl;
}
return 0;
}
B-戴好口罩!
题目
Input&&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
#output:
4
1
1
题解
1.本题实际上是求每个小团体的人数
2.每个团体只需要有一个代表人就好,因此我们可以想到利用并查集的思想,而且可
以使用路径压缩(仅仅关注领袖)
3.并查集的合并:实际上是把某一个集合的领袖变成另一个集合的领袖,(然而下一
次修改的时候我们就需要考虑更改节点领袖的个数问题,因此小树挂大树是我们需要
的。
C++代码
#include<iostream>
using namespace std;
const int maxn = 1e5;
int st[maxn],rak[maxn];
int n,m,num;
void init(){
for(int i = 0;i<n;i++){
st[i] = i;
rak[i]=1;
}
}
int find(int i){
if(st[i]!=i) return st[i]=find(st[i]);
return i;
}
bool unite(int x,int y){
x = find(x),y = find(y);
if(x==y) return false;
if(x>y) swap(x,y);
st[x] = y;
rak[y] = rak[x]+rak[y];
return true;
}
int main(){
int c,cha=-1;
while(cin>>n>>m){
if(m == n&&m==0) break;
init();
while(m--){
cha=-1;
cin>>num;
for(int i = 0;i<num;i++){
cin>>c;
if(cha!=-1) unite(c,cha);
cha=c;
}
}
cout<<rak[find(0)]<<endl;
}
return 0;
}
C-掌握魔法の东东 I
题目
Input&&Output:
Sample:
#input:
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
#output:
9
题解
1.本题我们可以将油田看成一个个点,而传送门实际上就是边,黄河之水天上来实际
上也是给这些油田传水
2.因此本题实际上是一个求最小遍历图的问题或者最小生成树问题,我们将天也看做
一块油田,他与每个油田都有连边,然后我们去求最小生成树就可以了
3.Kruskal算法:将点都放上,边排序每次取最小可以增强图的连通性的边(也就是
说已连通的边不需要再加边(下述代码所采用的)
4.prim算法:本质上与kruskal类似,只不过取边然后扩充点集,直至覆盖所有点
5.两种算法各有优劣,与边 点个数有关
6.连通性判断:实际上也是一个路径压缩的并查集
C++代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=1e5+500;
int m,n,root;
int u,v,w;
struct edge{
int u,v,w;
operator<(edge &x){
return w<x.w;
}
};
int conn[maxn];
vector<edge> ve(0);
int find(int i) {return conn[i]==i ? i : conn[i]=find(conn[i]);}
edge c;
int main(){
int MAX=0;
cin>>n>>m>>root;
for(int i=0;i<n+1;i++) conn[i]=i;
for(int i = 0;i<m;i++){
cin>>u>>v>>w;
c.u=u-1,c.v=v-1,c.w=w;
ve.push_back(c);
}
sort(ve.begin(),ve.end());
for(int i=0;i<ve.size();i++){
c=ve[i];
find(c.u),find(c.v);
if(conn[c.u]!=conn[c.v]){
conn[conn[c.u]]=conn[c.v];
if(c.w>MAX) MAX=c.w;
}
}
cout<<MAX<<endl;
return 0;
}
D-数据中心
题目
Input&&Output:
Sample:
#input:
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
#output:
4
题解
1.本题求流水线最小耗时,我们把它看成层实际上每层流水线同时开始,它到下一层
的最优时间取决于本层最大的时间,而整个流水线运行时,它的最优时间实际上是所
有层的最小时间(所有层的最大时间的最大值
2.我们这样去想,所有层最大时间的最大值不就是所有边的最大值么,那么我们实际
上是求一组变,它既能使流水线连通(图连通)而且边要尽可能的小,因此是一个最
小生成树问题
C++代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=1e5+500;
struct edge{
int u,v,w;
operator<(edge &x){
return w<x.w;
}
};
int conn[maxn];
vector<edge> ve(0);
int find(int i) {return conn[i]==i ? i : conn[i]=find(conn[i]);}
edge c;
int main(){
int n,w,u,v,len=0;
cin>>n;
for(int i=0;i<n+1;i++) conn[i]=i;
for(int i = 0;i<n;i++){
cin>>w;
c.u=n,c.v=i,c.w=w;
ve.push_back(c);
}
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
cin>>w;
c.u=i,c.v=j,c.w=w;
if(i<j) ve.push_back(c);
}
sort(ve.begin(),ve.end());
for(int i=0;i<ve.size();i++){
c=ve[i];
find(c.u),find(c.v);
if(conn[c.u]!=conn[c.v]){
conn[conn[c.u]]=conn[c.v];
len+=c.w;
}
}
cout<<len<<endl;
return 0;
}