数据结构第五次上机实验
7-1 图的深度优先搜索I (100 分)
无向图 G 有 n 个顶点和 m 条边。求图G的深度优先搜索树(森林)以及每个顶点的发现时间和完成时间。每个连通分量从编号最小的结点开始搜索,邻接顶点选择顺序遵循边的输入顺序。
在搜索过程中,第一次遇到一个结点,称该结点被发现;一个结点的所有邻接结点都搜索完,该结点的搜索被完成。深度优先搜索维护一个时钟,时钟从0开始计数,结点被搜索发现或完成时,时钟计数增1,然后为当前结点盖上时间戳。一个结点被搜索发现和完成的时间戳分别称为该结点的发现时间和完成时间
输入格式:
第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.
第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.
输出格式:
第1到n行,每行两个整数di和fi,用空格分隔,表示第i个顶点的发现时间和完成时间1≤i≤n 。
第n+1行,1个整数 k ,表示图的深度优先搜索树(森林)的边数。
第n+2到n+k+1行,每行两个整数u和v,表示深度优先搜索树(森林)的一条边<u,v>,边的输出顺序按 v 结点编号从小到大。
输入样例:
在这里给出一组输入。例如:
6 5
1 3
1 2
2 3
4 5
5 6
输出样例:
在这里给出相应的输出。例如:
1 6
3 4
2 5
7 12
8 11
9 10
4
3 2
1 3
4 5
5 6
解答:根据题目描述,这道题是一道普通的DFS,只需要多维护一个时钟,并把遍历路径保存即可。
具体代码如下:
#include<bits/stdc++.h>
using namespace std;
vector<int>p[100010];
queue<int>que;
int visited[50010];
int found[50010];
int com[50010];
void DFS(int v);
int num;
int clock0=0;
int k=0;
int bian[100010];
int main()
{
int n,i,data,m,u,v;
cin>>n>>m;
num=n;
for(i=0;i<m;i++)
{
cin>>u>>v;
p[u].push_back(v);
p[v].push_back(u);
}
for(i=1;i<=n;i++)
p[i].push_back(-1);
for(i=1;i<=n;i++)
visited[i]=0;
for(i=1;i<=n;i++)
{
if(visited[i]==0)
DFS(i);
}
for(i=1;i<=n;i++)
{
printf("%d %d\n",found[i],com[i]);
}
printf("%d\n",k);
for(i=1;i<=m;i++)
{
if(bian[i]!=0)
{
printf("%d %d\n",bian[i],i);
}
}
}
void DFS(int v)
{
int i;
clock0++;
found[v]=clock0;
visited[v]=1;
for(i=0;p[v][i]!=-1;i++)
{
if(visited[p[v][i]]==0)
{
k++;
bian[p[v][i]]=v;
DFS(p[v][i]);
}
}
clock0++;
com[v]=clock0;
}
从小到大保存边<u,v>,可以把v当作数组下标,存u,可以避免对边集排序,从小到大遍历若不为0输出即可。
7-2 圆 (100 分)
二维平面上有n 个圆。请统计:这些圆形成的不同的块的数目。
圆形成的块定义如下: (1)一个圆是一个块; (2)若两个块有公共部分(含相切),则这两个块形成一个新的块,否则还是两个不同的块。
输入格式:
第1行包括一个整数n,表示圆的数目,n<=8000。
第2到n+1行,每行3 个用空格隔开的数x,y,r。(x,y)是圆心坐标,r 是半径。所有的坐标及半径都是不大于30000 的非负整数。
输出格式:
1个整数,表示形成的块的数目。
输入样例:
在这里给出一组输入。例如:
2
0 0 1
1 0 2
输出样例:
在这里给出相应的输出。例如:
1
解答:如果只是仅仅使用纯暴力法可以过4个样例,不知道最后为什么错了,这道题有点类似与上一次的连通分量问题,可以用并查集,这里我用了DFS的方法,将相交的两个圆之间加一条边使其在同一个连通分量里,再用DFS求连通分量个数。
具体代码如下:
#include<bits/stdc++.h>
using namespace std;
vector<int> p[10000];
int x[10000],y[10000],r[10000];
int count0=0;
int visited[10000];
bool m(int a,int b)
{
if((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])-(r[a]+r[b])*(r[a]+r[b])<=0)
return true;
else
return false;
}
void DFS(int v)
{
visited[v]=1;
for(int i=1;i<p[v].size();i++)
{
if(visited[p[v][i]]!=1)
DFS(p[v][i]);
}
}
int main()
{
int n,i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
cin>>x[i]>>y[i]>>r[i];
p[i].push_back(0);
}
for(i=1;i<=n;i++)
{
for(j=i+1;j<=n;j++)
{
if(m(i,j))
{
p[i].push_back(j);
p[j].push_back(i);
}
}
}
for( i=1;i<=n;i++)
{
if(!visited[i])
{
DFS(i);
count0++;
}
}
printf("%d",count0);
return 0;
}
7-3 供电 (100 分)
要给N个地区供电。每个地区或者建一个供电站,或者修一条线道连接到其它有电的地区。试确定给N个地区都供上电的最小费用。
输入格式:
第1行,两个个整数 N 和 M , 用空格分隔,分别表示地区数和修线路的方案数,1≤N≤10000,0≤M≤50000。
第2行,包含N个用空格分隔的整数P[i],表示在第i个地区建一个供电站的代价,1 ≤P[i]≤ 100,000,1≤i≤N 。
接下来M行,每行3个整数a、b和c,用空格分隔,表示在地区a和b之间修一条线路的代价为c,1 ≤ c ≤ 100,000,1≤a,b≤N 。
输出格式:
一行,包含一个整数, 表示所求最小代价。
输入样例:
在这里给出一组输入。例如:
4 6
5 4 4 3
1 2 2
1 3 2
1 4 2
2 3 3
2 4 3
3 4 4
输出样例:
在这里给出相应的输出。例如:
9
解答:
这是一个求最小生成树的问题,但本身节点也有权值,可以将每个点的cost先置为本身建基站的权值,再进行prim算法。
#include<bits/stdc++.h>
using namespace std;
int cost[100010];
int cost0[100010];
int path[100010];
int cost_max[100010];
int sum,max0;
int u,v;
int visited[100010];
struct bian
{
int name1;
int name;
int cost;
bool operator<(bian a)
{
return cost>a.cost;
}
};
vector<bian>p[100010];
int main()
{
int n=0,m=0,i=0,j=0;
cin>>n;
cin>>m;
for(i=1;i<=n;i++)
{
cin>>cost0[i];
cost[i]=cost0[i];
}
if(m==0)
{
for(i=1;i<=n;i++)
sum+=cost0[i];
printf("%d",sum);
return 0;
}
bian a,b;
for(i=1;i<=m;i++)
{
cin>>a.name1;
cin>>a.name;
cin>>a.cost;
b.name1=a.name;
b.name=a.name1;
b.cost=a.cost;
p[a.name1].push_back(a);
p[b.name1].push_back(b);
}
for(j=1;j<=n;j++)
{
max0=100000;
for(i=1;i<=n;i++)
{
if(cost[i]<max0&&visited[i]==0)
{
max0=cost[i];
u=i;
}
}
visited[u]=1;
for(vector<bian>::iterator k = p[u].begin();k!=p[u].end();k++)
{
v=k->name;
if(k->cost<cost[v]&&visited[v]==0)
{
cost[v]=k->cost;
}
}
}
for(i=1;i<=n;i++)
{
sum+=cost[i];
}
printf("%d",sum);
}
7-4 发红包 (100 分)
新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,c1的红包钱数要比c2的多。每个员工的红包钱数至少要发888元,这是一个幸运数字。
公司想满足所有员工的要求,同时也要花钱最少,请你帮助计算。
输入格式:
第1行,两个整数n和m(n<=10000,m<=20000),用空格分隔,分别代表员工数和要求数。
接下来m行,每行两个整数c1和c2,用空格分隔,表示员工c1的红包钱数要比c2多,员工的编号1~n 。
输出格式:
一个整数,表示公司发的最少钱数。如果公司不能满足所有员工的需求,输出-1.
输入样例:
在这里给出一组输入。例如:
2 1
1 2
输出样例:
在这里给出相应的输出。例如:
1777
解答:这个问题员工的红包之间有相互限制关系,可以使用拓扑排序,c1的红包要比c2多,则可以把2排在1的前面进行拓扑排序,将开始为入度为0的节点的红包置为888,
之后入度减为0的点的红包置为前一个节点的红包+1,这样就能满足条件的情况下花费最少。
#include<bits/stdc++.h>
using namespace std;
vector<int>p[10000];
int count0[10000];
int money[10000];
stack<int> s;
int main()
{
int n,m,i,u,v,j;
int sum=0;
cin>>n>>m;
for(i=0;i<m;i++)
{
cin>>u>>v;
p[v].push_back(u);
}
for(i=1;i<=n;i++)
p[i].push_back(-1);
for(i=1;i<=n;i++)
{
for(j=0;p[i][j]!=-1;j++)
count0[p[i][j]]++;
}
for(i=1;i<=n;i++)
{
if(count0[i]==0)
{
s.push(i);
money[i]=0;
}
}
for(i=1;i<=n;i++)
{
if(s.empty())
{
printf("-1");
return 0;
}
v=s.top();
s.pop();
for(j=0;p[v][j]!=-1;j++)
{
count0[p[v][j]]--;
if(count0[p[v][j]]==0)
{
s.push(p[v][j]);
money[p[v][j]]=money[v]+1;
}
}
}
for(i=1;i<=n;i++)
{
sum+=money[i];
sum+=888;
}
printf("%d",sum);
}