程序设计作业 week6
本周作业相比前几周轻松了挺多(主要原因是课上给了主要的代码。主要内容为:
1.练习使用前向星方式存储图结构,以及在此方式下的图的遍历操作。
2.练习使用并查集,并与最小生成树结合运用。
A题 氪金带东
n台电脑形成树结构,要求输出每台电脑到达最远一台电脑的距离。
1.Sample Input and Output
Input
输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。
5
1 1
2 1
3 1
1 1
Output
对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N).
3
2
3
4
4
2.整体思路及代码
这道题首先需要明确,每个点到达的最远的点必定是两个直径端点中的一个(证明略)。由此可以利用转化思想,将每台电脑到达最远一台电脑的距离转化成两直径端点到某特定点距离的最大值。在此期间使用3次DFS,第一次对任意一点使用DFS找到它距离最长的另一端点,定为直径点1,再对该直径点使用DFS以找到直径点2,并在此期间更新每个点的距离数组。再对直径端点2使用DFS,更新距离数组,即可得到答案。
另利用前向星方式(本质是数组模拟链表)来存储图结构以优化复杂度。
#include<iostream>
const int MAXN=1e5+100;
using namespace std;
struct edge
{
public:
int u;
int v;
int w;
int nxt;
}Edges[MAXN];
int head[MAXN],tot; //tot为目前边的总个数
int vis[MAXN];
int Len[MAXN]; //第i个点到两个直径的最长距离
int n;
int p1,p2; //标记两个直径端点
int maxLen;
void init()
{
tot=0;
maxLen=0;
p1=0;
p2=0;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(Len,0, sizeof(Len));
}
void addEdge(int u,int v,int w)
{
Edges[tot].u=u;
Edges[tot].v=v;
Edges[tot].w=w;
Edges[tot].nxt=head[u];
head[u]=tot;
tot++;
}
void refresh()
{
maxLen=0;
memset(vis,0,sizeof(vis));
}
//找到第一个直径端点
void dfs1(int u, int len)
{
if(len>maxLen)
{
p1=u;
maxLen=len;
}
for(int i=head[u];i!=-1;i=Edges[i].nxt)
{
if(vis[Edges[i].v]==0)
{
vis[Edges[i].v]=1;
dfs1(Edges[i].v,len+Edges[i].w);
}
}
}
//找到另一个直径端点,并改变到各个点的距离大小。
void dfs2(int u, int len)
{
Len[u]=len;
if(len>maxLen)
{
p2=u;
maxLen=len;
}
for(int i=head[u];i!=-1;i=Edges[i].nxt)
{
if(vis[Edges[i].v]==0)
{
vis[Edges[i].v]=1;
dfs2(Edges[i].v,len+Edges[i].w);
}
}
}
//从第二个直径端点开始遍历点,更新最大距离
void dfs3(int u, int len)
{
if(len > Len[u])
Len[u]=len;
for(int i=head[u];i!=-1;i=Edges[i].nxt)
{
if(vis[Edges[i].v]==0)
{
vis[Edges[i].v]=1;
dfs3(Edges[i].v,len+Edges[i].w);
}
}
}
int main()
{
while(cin>>n) {
init();
for (int i = 2; i <= n; i++) {
int a, b;
cin >> a;
cin >> b;
addEdge(i, a, b);
addEdge(a, i, b);
}
vis[1] = 1;
dfs1(1, 0);
refresh();
vis[p1] = 1;
dfs2(p1, 0);
refresh();
vis[p2] = 1;
dfs3(p2, 0);
for (int i = 1; i <= n; i++)
cout << Len[i] << endl;
}
return 0;
}
总结
我递交的解使用了三遍DFS,也就写了3略有不同个DFS函数。但这三个可以合并成一个。
void DFS(int x,int fa,int d)
{
dis[x]=d;
ans[x]=max{ans[x],dis[x]};
if(dis[x]>dis[s]) s=x;
for(auto it :: G(x))
if(it.first != fa) DFS(it.first,x,d+it.second);
}
B题 戴好口罩
新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
如果一个感染者走入一个群体,那么这个群体需要被隔离!
小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。
1.Sample Input and Output
Input
多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
学生编号为0~n-1
小A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
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
2.整体思路及代码
主要练习并查集的使用,注意压缩路径return par[x]==x ? x:par[x]=find(par[x]);
可以使O(n)降到常数级。
#include<iostream>
using namespace std;
const int maxn=3e4+100;
int par[maxn],rnk[maxn];
void init(int n)
{
for(int i=0;i<n;i++)
{
par[i]=i;
rnk[i]=1;
}
}
int find(int x)
{
return par[x]==x ? x:par[x]=find(par[x]);
}
bool unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y) return false;
par[x]=y;
rnk[y]+=rnk[x];
return true;
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m) && ! (n==0&&m==0))
{
init(n);
while(m--)
{
int num,last=-1;
scanf("%d",&num);
while(num--)
{
int p;
scanf("%d",&p);
if(last!=-1)
unite(p,last);
last=p;
}
}
printf("%d\n",rnk[find(0)]);
}
return 0;
}
C题 掌握魔法的东东
浇水灌田。水的获取方法有两种,一种是水从天降,需要的成本为wi,另一种为从已经获得水的田中引水,成本为pij。求所有的田都灌水的最小成本。
1.Sample Input and Output
Input
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Output
东东最小消耗的MP值
9
2.整体思路及代码
可以将从天而降的水源看作0号点,以看作为最小生成树问题,使用Kruskal解决。将边的权重升序排序,利用并查集依次判断是否能插入到生成树中,将能插入的边累加和返回并输出。由于两点之间的来去成本相同,故在输入矩阵时可以进行小剪枝,
if (i <= j) continue;
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
#define rep(i,s,t) for(int i=s;i<=t;i++)
const int maxn=3e2+100;
struct Edge
{
int u,v,w;
bool operator<(const Edge &e)const
{
return w<e.w;
}
}Es[maxn*maxn];
int tot=0,n;
//并查集部分
int par[maxn];
void init(int n)
{
for(int i=0;i<=n;i++)
par[i]=i;
}
int find(int x)
{
return par[x]==x ? x:par[x]=find(p2ar[x]);
}
bool unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y) return false;
par[x]=y;
return true;
}
int kruskal()
{
init(n);
sort(Es,Es+tot);
int cnt=0,ans=0;
for(int i=0;i<tot;i++)
{
if (unite(Es[i].u, Es[i].v))
{
ans += Es[i].w;
cnt++;
if (cnt == n)
return ans;
}
}
return -1;
}
int main()
{
cin>>n;
rep(i,1,n)
{
cin>>Es[tot].w;
Es[tot].u=0;
Es[tot].v=i;
tot++;
}
rep(i,1,n) {
rep(j, 1, n) {
cin >> Es[tot].w;
if (i <= j) continue;
Es[tot].u = i;
Es[tot].v = j;
tot++;
}
}
cout<<kruskal();
return 0;
}
D题 CSP 数据中心
1.Sample Input and Output
Input
4
5
1
1 2 3
1 3 4
1 4 5
2 3 8
3 4 2
Output
4
2.整体思路及代码
题目要求即为给定无向图,求解一颗生成树,使得最大边的边权最小。要使的最大边的边权最小,也即求该无向图的最小生成树。使用Kruskal的大体解题过程与T3类似,只不过将要返回的ans储存为ans = max(ans,Es[i].w);
#include<iostream>
#include<algorithm>
using namespace std;
#define rep(i,s,t) for(int i=s;i<=t;i++)
const int N=50005;
const int M=100005;
struct Edge
{
int u,v,w;
bool operator<(const Edge &e)const
{
return w<e.w;
}
}Es[M];
int n,m,rt;
//并查集部分
int par[N];
void init(int n)
{
for(int i=1;i<=n;i++)
par[i]=i;
}
int find(int x)
{
return par[x]==x ? x:par[x]=find(par[x]);
}
bool unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y) return false;
par[x]=y;
return true;
}
int kruskal()
{
//init(n);
sort(Es+1,Es+1+m);
int cnt=0,ans=0;
for(int i=1;i<=m;++i)
{
if (unite(Es[i].u, Es[i].v))
{
ans = max(ans,Es[i].w);
cnt++;
if (cnt == n-1)
break;
}
}
return cnt==n-1 ? ans : -1;
}
int main()
{
cin>>n;
cin>>m;
cin>>rt;
init(n);
rep(i,1,m)
{
cin>>Es[i].u;
cin>>Es[i].v;
cin>>Es[i].w;
}
cout<<kruskal();
return 0;
}