数据结构荣誉课第五次上机实验
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
解题思路:设置标记数组visit,按照序号从小到大开始遍历节点,如果该节点visit为0,则以他为根节点遍历连通分量,遍历方法用DFS遍历,遍历过记得打上标记,同时遍历开始更新开始遍历的时间,所有邻接节点遍历过后更新结束时间,每次遍历邻接节点将邻接边保存进pair数组
并且跟新深度搜索树的边数,最后将边按照要求排序输出即可。
完整代码:
#include <bits/stdc++.h>
using namespace std;
vector<int> s[50010];
int n,m;
int Find[50000]={0},finish[50010]={0},visit[50010]={0};
int Time=0;
int bian=0;
pair<int,int>q[100010];
int key=0;
bool compare(pair<int,int>a,pair<int,int> b){
if(a.second<b.second)
return true;
else
return false;
}
void DFS(int k){
visit[k]=1;
Time++;
Find[k]=Time;
for(vector<int>::iterator it=s[k].begin();it!=s[k].end();it++){
if(visit[*it]==0){
q[key++]=make_pair(k,*it);
bian++;
DFS(*it);
}
}
Time++;
finish[k]=Time;
}
int main(){
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
s[x].push_back(y);
s[y].push_back(x);
}
for(int i=1;i<=n;i++){
if(visit[i]==0){
DFS(i);
}
}
for(int i=1;i<=n;i++){
printf("%d %d\n",Find[i],finish[i]);
}
printf("%d\n",bian);
sort(q,q+bian,compare);
for(int i=0;i<bian;i++)
printf("%d %d\n",q[i].first,q[i].second);
}
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
解题思路:
将圆看成一个点,如果两个圆相交或者相切,则认为两点之间有边,最后块的数量其实就等于
点构成图连通分量的数量,可以采用并查集。注意计算圆心间的距离时注意数据类型使用,
srqt用平方代替,这样精度更高,同时计算机几何问题时尽量避免小数的运算。
完整代码:
#include <bits/stdc++.h>
using namespace std;
int n;
int x[8010]={0},y[8010]={0},r[8010]={0};
int father[8010];
int num=0;
int Find(int m){
if(father[m]==m)return m;
else
return father[m]=Find(father[m]);
}
void combine(int l,int e){
int x=Find(l);
int y=Find(e);
if(x==y)return ;
else father[y]=x;
}
int dis(int x1,int y1,int r1,int x2,int y2,int r2){
if(sqrt(((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)))<=r1+r2)
return 0;
else
return 1;
}
int main(){
int a,b,c;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&x[i],&y[i],&r[i]);
}
for(int i=1;i<=n;i++)
father[i]=i;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
int o=dis(x[i],y[i],r[i],x[j],y[j],r[j]);
if(o==0)
combine(i,j);
}
}
for(int i=1;i<=n;i++) {
if(father[i]==i)
num++;
}
printf("%d",num);
}
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
解题思路:
这道题很显然是最小生成树问题,这道题比较特殊的是可以建立一个发电而不是连接两个城市,因为有可能建立发电站的代价更小,所以一个连通分量可能不仅仅只是根节点有一个发电站,还有就是一定要保证最后至少有一个发电站,也就是需要确定最小生成树的开始节点,这里选建立代价最小的节点,分两种算法解答:
prim:
首先将lowcost赋值为建立发电站的代价,因为如果建立发电站代价更小的话可以及时更新,生成节点的所有邻接边,然后采用prim算法即可。
#include <bits/stdc++.h>
using namespace std;
int n,m;
int p[10010];
const int INF=0x3f3f3f;
int Min=1;
int sum=0;
int lowcost[10010],mark[10010];
vector<pair<int,int> >s[10010];
int main(){
int x,y,z;
int u;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&p[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
s[x].push_back(make_pair(y,z));
s[y].push_back(make_pair(x,z));
}
for(int i=1;i<=n;i++){
lowcost[i]=p[i];
mark[i]=0;
}
for(int i=1;i<=n;i++){
if(p[i]<p[Min])Min=i;
}
mark[Min]=1;lowcost[Min]=p[Min];
for(vector<pair<int,int> >::iterator it=s[Min].begin();it!=s[Min].end();it++){
if(it->second<lowcost[it->first]){
lowcost[it->first]=it->second;
}
}
for(int j=1;j<n;j++){
int dis=INF;
for(int i=1;i<=n;i++){
if(lowcost[i]<dis&&mark[i]==0){
dis=lowcost[i];
u=i;
}
}
mark[u]=1;
for(vector<pair<int,int> >::iterator it=s[u].begin();it!=s[u].end();it++){
if(it->second<lowcost[it->first]&&mark[it->first]==0)
lowcost[it->first]=it->second;
}
}
for(int i=1;i<=n;i++){
sum+=lowcost[i];
}
printf("%d",sum);
}
Kruskal算法:
取最小边用排序实现,判环用并查集实现。
#include <bits/stdc++.h>
using namespace std;
int n,m;
typedef struct toys{
int x;
int y;
int cost;
}T;
int sum=0;
int father[10010];
int p[10010];
T key[100010];
bool compare(T a,T b){
return a.cost<b.cost;
}
int Find(int a){
if(father[a]==a)return a;
else return father[a]=Find(father[a]);
}
void combine(int x,int y){
int a=Find(x);
int b=Find(y);
if(a!=b) father[b]=a;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&p[i]);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&key[i].x,&key[i].y,&key[i].cost);
}
int i=m+1;
int k=1;
while(i<=m+n){
key[i].x=0;
key[i].y=k;
key[i].cost=p[k];
k++;
i++;
}
sort(key+1,key+m+n+1,compare);
for(int i=1;i<=n;i++)
father[i]=i;
for(int i=1,k=1;k<=n;i++){
if(Find(key[i].x)!=Find(key[i].y)){
combine(key[i].x,key[i].y);
sum+=key[i].cost;
k++;
}
}
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
解题思路:
因为员工彼此的红包金额有限制,分析题可知是拓扑排序问题,无法满足所有人的要求判断是否有环路即可,注意统计每个人的出度和入度:
#include <bits/stdc++.h>
using namespace std;
int n,m;
vector<int> s[10010];
int Count[10010];
int money[10010];
int flag=0;
int sum=0;
int num=0;
bool Toys(){
queue<int>q;
for(int i=1;i<=n;i++){
if(!Count[i])
q.push(i);
}
while(!q.empty()){
int k=q.front();
q.pop();
num++;
for(vector<int>::iterator it=s[k].begin();it!=s[k].end();it++){
money[*it]=max(money[*it],money[k]+1);
Count[*it]--;
if(Count[*it]==0)q.push(*it);
}
}
return num==n;
}
int main(){
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
s[y].push_back(x);
Count[x]++;
}
for(int i=1;i<=n;i++)
money[i]=888;
if(Toys()){
for(int i=1;i<=n;i++) {
sum+=money[i];
}
printf("%d",sum);
}else{
printf("-1");
}
}