一、图的深度优先搜索
题目
无向图 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,只不过题目中多了两个要求,一个是加了时间戳,再一个就是要求把深搜时所有的边<u,v>都输出而且是按照 v 递增的顺序输出。第一个问题很好解决,就是在dfs这个函数的开始和结尾分别将时间戳加一并且存入记录一个节点被发现的时间以及搜素结束的时间,我是用了两个整形数组来分别记录一个节点被发现和搜索结束的时间。第二个问题就是搜索树边的问题,这个问题解决的方式就比较多了,可以在深搜时先将边都存入一个边的数组,然后再按照 v 的递增序将边输出,或者建立一个优先级队列随时将边存入且保持 v 递增序,我采用的是前者。
参考代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 100000 + 6;
vector<int> edge[maxn];
int t = 0;
int l[maxn], r[maxn]; //发现与结束的时间
int vis[maxn];
void AddEdge(int from, int to){
edge[from].push_back(to);
edge[to].push_back(from);
}
struct Edge{
int u, v;
bool operator < (const Edge &e2)const{
return v < e2.v;
}
};
struct Edge e[maxn];
int num = 0; // 深搜边的数目
void DFS(int x){
if(vis[x] == 1) return ;
l[x] = ++t;
vis[x] = 1;
for(int i = 0; i < edge[x].size(); i++){
int tmp = edge[x][i];
if(vis[tmp] == 0) {
e[num].u = x;
e[num].v = tmp;
num ++;
DFS(tmp);
}
}
r[x] = ++t;
}
int main(){
int m,n;
scanf("%d%d",&n, &m);
for(int i = 1; i <= m; i++){
int u, v; scanf("%d%d",&u, &v);
AddEdge(u, v);
}
for(int i = 1; i <= n; i++) vis[i] = 0;
for(int i = 1; i <= n; i++){
if(vis[i] == 0) DFS(i);
}
for(int i = 1; i <= n; i++) printf("%d %d\n",l[i],r[i]);
printf("%d\n",num);
sort(e, e + num);
for(int i = 0; i < num; i++){
printf("%d %d\n",e[i].u, e[i].v);
}
return 0;
}
二、圆
题目
二维平面上有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
解题思路
圆的这个题我们不难发现这是一个并查集的问题,只要两个圆相交或者相切,他们就属于同一个类,所以只需进行一遍循环来找相交或者相切的圆进行合并,每合并一次块的数目就减一,所以只需要在调用合并函数是将计数器加一,最后用圆的数目减去计数器即可。代码如下:
参考代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 8000 + 1;
typedef struct cir{
long long x, y, r;
}cir;
cir jihe[maxn];
int t = 0;
int father[maxn];
void MAKE_SET(int x)
{
father[x] = 0; //根节点
}
int Find(int x){
if(father[x]<=0) return x;
int fx=Find(father[x]);
father[x]=fx;
return fx;
}
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;
}
t ++;
}
int main(){
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i++){
long long x, y, r; scanf("%lld%lld%lld",&x, &y, &r);
jihe[i].x = x; jihe[i].y = y; jihe[i].r = r;
}
for(int i = 1; i <= n; i++) MAKE_SET(i);
for(int i = 1; i <= n; i++){
for(int j = i + 1;j <= n; j++){
long long d = (jihe[i].x - jihe[j].x)*(jihe[i].x - jihe[j].x)+(jihe[i].y - jihe[j].y)*(jihe[i].y - jihe[j].y);
if(d <= (jihe[i].r+jihe[j].r)*(jihe[i].r+jihe[j].r)){
if(Find(i)!=Find(j)) Union(i,j);
}
}
}
printf("%d",n-t);
return 0;
}
三、供电
题目
要给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
解题思路
第一次看这道题目的时候其实没有看出来应该用最小支撑树来做,也是听了同学们讲题才恍然大悟,只不过这道题与最小支撑树还是有点出入的,这道题要求可以在本地间基站,这就代表着,如果从其他的地方修线道可能还没搭建一个基站省钱,即使是这样它也是一个最小支撑树的问题,只不过初始时 lowcost 数组存的是间基站的费用(相当一个虚边),其实也不许要把最小支撑树求出来,只需将所有的 lowcost[i] 更新为最小值即可,最后将 N 个 lowcost[i] 相加即为所求。代码如下:
参考代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 50000 + 1;
vector<pair<int, int> > edge[maxn];
int P[maxn];
int mina = 9999, pos;
int lowcost[maxn], vex[maxn], mark[maxn];
void Prim(int N){
for(int i = 1; i <= N; i++){
lowcost[i] = P[i];
mark[i] = 0;
vex[i] = -1;
}
mark[pos] = 1;
for(vector<pair<int,int> >::iterator iter = edge[pos].begin(); iter!=edge[pos].end(); iter++ ){
int k = (*iter).first;
if(lowcost[k] > (*iter).second) {lowcost[k] = (*iter).second; vex[k] = pos; }
}
int u,idist;
for(int j = 1; j < N; j++){
idist = maxn;
for(int i = 1; i <= N; i++)
if(lowcost[i] < idist && mark[i] == 0){
idist = lowcost[i]; u = i;
}
mark[u] = 1;
for(vector<pair<int,int> >::iterator iter = edge[u].begin(); iter!=edge[u].end(); iter++ ){
int v = (*iter).first;
if((*iter).second < lowcost[v] && mark[v] == 0){
lowcost[v] = (*iter).second;
vex[v] = u;
}
}
}
}
int main(){
int N, M; scanf("%d%d",&N, &M); //N 是地区数; M 是修线路的方案数
for(int i = 1; i <= N; i++){
scanf("%d",&P[i]);
if(P[i] < mina) { mina = P[i]; pos = i; }
}
for(int i = 1; i <= M; i++){
int a, b, c; scanf("%d%d%d", &a, &b, &c);
edge[a].push_back(make_pair(b,c));
edge[b].push_back(make_pair(a,c));
}
Prim(N);
int money = 0;
for(int i = 1; i <= N; i++){
money += lowcost[i];
}
printf("%d",money);
return 0;
}
四、发红包
题目
新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,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多,有此可以想到应该用拓扑排序来解决这道题,尤其是看到公司不能满足所有员工要求输出-1这里,我更加肯定应该用拓扑排序来解决。
不过在建图的时候需要注意一个地方就是应该由c2指向c1(因为c1要求比c2多)所以建图是应该反过来建图。接下来就是拓扑排序了。在解决钱数的问题时我把入度为零的员工赋值为0,接下来每到下一个点就将该点的 sign[i] 与上一个点的 sign + 1 比较如果大于则更新否则不变。
最后将所有的 sign 值相加再与888*n相加即为所求最小钱数。
参考代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std; //拓扑排序做法
const int maxn = 20000 + 1;
vector<int> rex[maxn];
int sign[maxn]; //记录钱数
void AddEdge(int to, int from){
rex[from].push_back(to);
}
int main(){
int m, n;
scanf("%d%d",&n, &m);
for(int i = 1; i <= n; i++) sign[i] = 0;
for(int i = 1; i <= m; i++){
int c1, c2; scanf("%d%d",&c1, &c2);
AddEdge(c1, c2);
}
int count[maxn];//记录所有顶点的入度
int i,j,k;
//初始化
for(i=1;i<=n;i++){
count[i]=0;
}
for(i=1;i<=n;i++){
for(int a = 0; a < rex[i].size(); a++)
count[rex[i][a]]++;
}
stack<int> S;
for(i=1;i<=n;i++){
if(count[i]==0) S.push(i);
}
for(i=1;i<=n;i++){
if(S.empty()) {printf("-1"); return 0;} //存在环, 即不能满足所有人的要求
j=S.top();S.pop();
for(int a = 0; a < rex[j].size(); a++){
k = rex[j][a];
count[k]--;
if(count[k]==0) S.push(k);
if(sign[j] +1 > sign[k]) sign[k] = sign[j] + 1;
}
}
int money = 888 * n;
for(int i = 1; i <= n;i ++){
money += sign[i];
}
printf("%d",money);
return 0;
}