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
后话:
1.本题实质是一个DFS深搜,加上时间类似tarjan()算法的时间戳,在递归入口与出口赋值和加一,在递归入口前将遍加入优先队列,输出走过的边。
2.样例四与样例五超内存:存图方式原因:
(1)链接链表存图会导致加上存边的优先队列会导致内存超限。样例四与五存图就占一半的存储空间。
(2).使用链式后向星(前向星和邻接链表+消递归dfs相同,不能保证访问顺序)内存不会超限。
(3)仍未解决的问题: 图的方法目前来说有三种,1.邻接矩阵(空间复杂度O(n^2)2.邻接矩阵,按照我看过的大部分博客来说,这种存图被认为是时间复杂度与空间复杂度都是最小的一种方法(3)链式前向星,较为中庸的一种方法,优点在于写法简单。所以本题中,对于邻接链表和链式前向星的空间复杂度的比较出现了相反的实验结果。 原因仍未解决。
注释掉dfs步骤后使用内存,其中大部分为建图花费
图1:链式前向星
图2.邻接链表
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 500005;//点数最大值
int n, m, cnt;//n个点,m条边
typedef struct Edge
{
int u,to, w, next;//终点,边权,同起点的上一条边的编号
} Edge;
Edge edge[2*maxn];//边集
int head[maxn];//head[i],表示以i为起点的第一条边在边集数组的位置(编号)
void init()//初始化
{
for (int i = 0; i <= n; i++) head[i] = -1;
cnt = 0;
}
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
edge[cnt].u=u;
edge[cnt].to = v; //终点
edge[cnt].w = w; //权值
edge[cnt].next =-1;
int num=head[u];
if(head[u]!=-1)
{
while(edge[num].next!=-1) num=edge[num].next;
edge[num].next=cnt;
}
else
{
head[u] = cnt;//更新以u为起点上一条边的编号
}
cnt++;
}
struct cmp {
bool operator()(Edge a, Edge b)
{
return a.to > b.to;
}
};
priority_queue<Edge, vector<Edge>, cmp> ee;
int times[50001], timee[50001],tt;
void DFS(int v, int u)
{
tt++;
times[v] = tt;
for (int j = head[v]; j != -1; j = edge[j].next)
{
if (!times[edge[j].to])
{
DFS(edge[j].to, v);
ee.push(edge[j]);
}
}
timee[v] =++tt;
}
int main()
{
cin >> n >> m;
int u, v, w=0;
init();//初始化
for (int i = 1; i <= m; i++)//输入m条边
{
cin >> u >> v ;
add_edge(u, v, w);
add_edge(v, u, w);
}
for(int i=1;i<=n;i++)
{
if(!times[i]) DFS(i,0);
}
for (int i = 1; i <= n; i++)
{
printf("%d %d\n", times[i], timee[i]);
}
int num = ee.size();
cout<<num<<"\n";
for (int i = 1; i <= num; i++)
{
cout<< ee.top().u<<" "<<ee.top().to;
ee.pop();
if (i != num) cout<<"\n";
}
return 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
思路:
1.本题实质是求图的联通分量,只是图不是明确给出的,而是潜在存在且唯一的图,具体方法可以使用并查集实现。
2.注意 开平方的sqrt()函数可能会造成精度损失。
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <vector>
#include <math.h>
using namespace std;
typedef struct vertex {
int x,y,r;
struct edge* next;
}vertex;
typedef struct graph {
int vv, ee;
vector<vertex> verlist;
}graph;
graph* g = new graph;
int father[100000];
int FIND(int x)
{
while (father[x] >0) x = father[x];
return x;
}
void UNION(int x, int y)
{
int fx = FIND(x), fy = FIND(y);
if (fx == fy) return;
if (father[fx] < father[fy]) father[fy] = fx;
else {
if (father[fx] == father[fy]) father[fy]--;
father[fx] = fy;
}
}
int main()
{
scanf("%d",&g->vv);
int num=g->vv;
g->ee = 0;
for (int i = 0; i < g->vv; i++)
{
int x, y,r;
vertex* p = new vertex;
cin >> x >> y>>r;
p->x= x;
p->y=y;
p->r=r;
g->verlist.push_back(*p);
}
for (int i = 1,k=0; i <= num; i++)
{
for (int j = i + 1; j <= num; j++)
{
if (FIND(i) != FIND(j))
if(sqrt((g->verlist[i-1].x - g->verlist[j-1].x)*(g->verlist[i-1].x - g->verlist[j-1].x) + (g->verlist[i-1].y - g->verlist[j-1].y)*(g->verlist[i-1].y - g->verlist[j-1].y))<=g->verlist[i-1].r+g->verlist[j-1].r)
{
UNION(i, j);
g->vv--;
}
}
}
printf("%d",g->vv);
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
分析
1.本题的基础是最小生成树,但运用于实际是时,会出现一种特殊情况:在某一点建立电站的代价要小于修线路的代价。 在这种特殊情况的约束下,kruskal算法难以处理,所以选择prim算法,通过加入一条反身边来代表在自身处建立电厂的代价。
代码
#include <stdio.h>
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
using namespace std;
typedef struct edge {
int ii;
int cost;
struct edge* next;
} edge;
typedef struct vertex {
int ii;
int cost;
struct edge* next;
}vertex;
typedef struct graph {
int vv, ee;
vector<vertex> verlist;
}graph;
graph* g = new graph;
int vex[100005], lc[100005], mark[10005];
int cost[100005];
void Prim(int s)
{
for (int i = 1; i <=g->vv; i++)
{
lc[i] = cost[i];
mark[i] = 0;
}
for (int i = 1; i <=g->vv; i++)
{
int lw = 1000005, v;
for (int i = 1; i <= g->vv; i++)
{
if (!mark[i] && lw > lc[i])
{
lw = lc[i];
v = i;
}
}
mark[v] = 1;
edge* p = g->verlist[v-1].next;
for (p; p; p = p->next)
{
if (p->cost < lc[p->ii] && !mark[p->ii])
{
lc[p->ii] = p->cost;
}
}
}
}
int min_cost1=100000;
int main()
{
scanf("%d%d", &g->vv, &g->ee);
for (int i = 1; i <= g->vv; i++)
{
vertex* p = new vertex;
p->ii = i ;
p->next = NULL;
g->verlist.push_back(*p);
}
for (int i = 1; i <= g->vv; i++)
{
scanf("%d", &cost[i]);
if(min_cost1>cost[i]) min_cost1=cost[i];
}
for (int i = 0; i < g->ee; i++)
{
int m, n,cost;
cin >> m >> n>>cost;
edge* q = new edge;
q->ii = n;
q->cost = cost;
q->next = NULL;
if (!g->verlist[m-1].next)
{
g->verlist[m-1].next = q;
}
else
{
edge* head = g->verlist[m-1].next;
while (head->next)
{
head = head->next;
}
head->next = q;
}
q = new edge;
q->ii = m;
q->cost = cost;
q->next = NULL;
if (!g->verlist[n-1].next)
{
g->verlist[n-1].next = q;
}
else
{
edge* head = g->verlist[n-1].next;
while (head->next)
{
head = head->next;
}
head->next = q;
}
}
int num=0;long long all_cost=0;
for(int i=1;i<=g->vv;i++)
{
if(!mark[i])
{
Prim(i);num++;
}
}
for(int i=1;i<=g->vv;i++) all_cost+=lc[i];
cout<<all_cost;
}
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
分析:
1.本题基本思路是拓扑排序,每一个人指向的最后出队的人的工资数+1即为他的工资数,而失败的情况代表着指向图中出现了环。
代码
#include <stdio.h>
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
using namespace std;
typedef struct edge {
int ii;
int cost;
struct edge* next;
} edge;
typedef struct vertex {
int ii;
int money;
struct edge* next;
}vertex;
typedef struct graph {
int vv, ee;
vector<vertex> verlist;
}graph;
graph* g = new graph;
struct cmp{
bool operator()(int a,int b)
{
return a>b;
}
};
long long sum;
int count[10005];//结点个数
stack<int> tt;
void tope()
{
for (int i = 1; i <=g->vv; i++)
count[i] = 0;
for (int i = 0; i < g->vv; i++)
{
edge* p;
p = g->verlist[i].next;
for (p; p; p = p->next)
{
count[p->ii]++;
}
}
for (int i = 1; i <= g->vv; i++)
if (count[i]==0)
{
tt.push(i);
g->verlist[i-1].money=888;
}
for(int i=1;i<=g->vv;i++)
{
if(tt.empty())
{
printf("-1");
return ;
}
int v = tt.top();
tt.pop();
sum+=g->verlist[v-1].money;
for (edge* p = g->verlist[v-1].next; p; p = p->next)
{
count[p->ii]--;
if (count[p->ii] == 0)
{
g->verlist[p->ii-1].money=g->verlist[v-1].money+1;
tt.push(p->ii);
}
}
}
printf("%lld",sum);
}
int main()
{
scanf("%d%d",&g->vv,&g->ee);
for (int i = 1; i <= g->vv; i++)
{
vertex* p = new vertex;
p->ii = i ;
p->next = NULL;
g->verlist.push_back(*p);
}
for (int i = 0; i < g->ee; i++)
{
int m, n;
scanf("%d%d",&n,&m);
edge* q = new edge;
q->ii = n;
q->cost = 0;
q->next = NULL;
if (!g->verlist[m-1].next)
{
g->verlist[m-1].next = q;
}
else
{
edge* head = g->verlist[m-1].next;
while (head->next)
{
if(head->ii==n) break;
head = head->next;
}
if(head->ii!=n) head->next = q;
}
}
tope();
}