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;
const int INF=1e9+5;
typedef long long ll;
vector<int>vec[50001];
queue<int>q;
struct edge
{
int xx,yy;
}Edge[100001];
int n,m,sumOfEdge,ttime,tot;
int findtime[50001],endtime[50001];
int bj[50001];
bool cmp(edge ax,edge bx){
return ax.yy<bx.yy;
}
void dfs(int u)
{
if(bj[u]) return;
bj[u]=1;
findtime[u]=++ttime;
for(int j=0;j<vec[u].size();j++)
{
int net=vec[u][j];
if(!bj[net])
{
dfs(net);
sumOfEdge++;
Edge[++tot].xx=u;
Edge[tot].yy=net;
}
}
endtime[u]=++ttime;
}
int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
vec[x].push_back(y);
vec[y].push_back(x);
}
for(int i=1;i<=n;i++){
if(!bj[i]) dfs(i);
}
for(int i=1;i<=n;i++){
printf("%d %d\n",findtime[i],endtime[i]);
}
printf("%d\n",tot);
sort(Edge+1,Edge+tot+1,cmp);
for(int i=1;i<=tot;i++){
printf("%d %d\n",Edge[i].xx,Edge[i].yy);
}
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
思路:
这道题和上次的一道题目有些相似,我是使用了并查集解决的这道题。注意判断圆的相交相切时不要使用sqrt()函数,会有精度损失。用平方比较要开long long或double数组,否则会溢出。
#include<bits/stdc++.h>
using namespace std;
const int INF=1e9+5;
typedef long long ll;
int n,countt;
int x[8001],y[8001],r[8001];
int father[8001];
int Find(int v)
{
if(father[v]==v) return v;
return father[v]=Find(father[v]);
}
void Union(int x,int y)
{
father[Find(y)]=Find(x);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
father[i]=i;
}
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&x[i],&y[i],&r[i]);
}
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
if((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])<=(r[i]+r[j])*(r[i]+r[j]))
Union(i,j);
}
}
for(int i=1;i<=n;i++){
if(father[i]==i) countt++;
}
cout<<countt;
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
思路一:
引入虚点的kruskal算法。每个城市建的发电站的费用可以看成是“0号发电站”与这个城市相连的费用,这样保证了至少有一个电站。之后就可以使用kruskal算法和并查集完成此题。
#include<bits/stdc++.h>
using namespace std;
const int INF=1e9+5;
typedef long long ll;
int n,m,s;
int father[10001];
ll countt;
struct Node{
int x,y,z;
}node[60007];
bool cmp(Node aa,Node bb){
return aa.z<bb.z;
}
int Find(int v){
if(father[v]==v) return v;
return father[v]=Find(father[v]);
}
void Union(int x,int y){
father[Find(y)]=Find(x);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++) father[i]=i;
int x,y,t;
for(int i=1;i<=n;i++){
scanf("%d",&x);
node[i].x=0;
node[i].y=i;
node[i].z=x;
}
for(int j=n+1;j<=n+m;j++){
scanf("%d%d%d",&x,&y,&t);
node[j].x=x;node[j].y=y;node[j].z=t;
}
sort(node+1,node+1+m+n,cmp);
for(int i=1;i<=m+n;i++)
{
int fx=Find(node[i].x),fy=Find(node[i].y);
if(fx!=fy){ father[fx]=fy;s++;countt+=node[i].z;}
if(s==n) break;
}
printf("%d",countt);
}
思路二
prim算法:使用建电站的费用代替无穷初始化lowcost,读入过程中选出一个最小的建站费用作为开始点,之后每次选择一个最小的轻边,进行n-1次,就完成了最小生成树的建立。这时只需要把lowcost数组中的数据加和,就得到了最小费用。(虽然算法的时间复杂度是O(n2),但还是能过,10000个数据能在400ms内跑完)
#include<bits/stdc++.h>
using namespace std;
const int INF=1e9+5;
const int maxn=1e4+9;
typedef long long ll;
vector<pair<int,int> >vec[maxn];
int n,m,u=1,least=INF;
int lowcost[maxn];
int bj[maxn];
ll countt;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>lowcost[i];
if(least>lowcost[i]){
least=lowcost[i];
u=i;
}
}
bj[u]=1;
int x,y,t;
for(int i=1;i<=m;i++){
cin>>x>>y>>t;
vec[x].push_back(make_pair(y,t));
vec[y].push_back(make_pair(x,t));
}
for(int k=1;k<n;k++)
{
for(int i=0;i<vec[u].size();i++){
int m=vec[u][i].first;
int co=vec[u][i].second;
if(lowcost[m]>co&&!bj[m]){
lowcost[m]=co;
}
}
int minn=INF;
for(int i=1;i<=n;i++)
{
if(lowcost[i]<minn&&!bj[i])
{
minn=lowcost[i];
u=i;
}
}
bj[u]=1;
}
for(int i=1;i<=n;i++){
countt+=lowcost[i];
}
cout<<countt;
}
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
思路:
拓扑排序,输入时统计入度(这里我采用了逆序存图,使红包小的指向红包大的,便于拓扑排序后续操作)度为0的点入队,并将其红包设为888,每次循环出队一个元素,更新其指向的元素的入度和红包数直到队空,如果还存在入度不为0的点,则证明有环,输出-1。
#include<bits/stdc++.h>
using namespace std;
const int INF=1e9+5;
typedef long long ll;
vector<int>vec[10001];
queue<int>q;
int n,m;
int indeg[10001];
int maxx[10001];
ll countt;
int main()
{
scanf("%d%d",&n,&m);
if(n==0){cout<<0;exit(0);}
if(m==0){cout<<n*888;exit(0);}
int x,y;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
vec[y].push_back(x);
indeg[x]++;
}
for(int i=1;i<=n;i++){
if(indeg[i]==0) q.push(i),maxx[i]=888;
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int j=0;j<vec[now].size();j++)
{
if(maxx[vec[now][j]]<=maxx[now]){
maxx[vec[now][j]]=maxx[now]+1;
}
indeg[vec[now][j]]--;
if(indeg[vec[now][j]]==0) q.push(vec[now][j]);
}
}
for(int i=1;i<=n;i++)
{
if(indeg[i]!=0) {cout<<-1;exit(0);}
}
for(int i=1;i<=n;i++){
countt+=maxx[i];
}
cout<<countt;
return 0;
}