JLU数据结构第五次上机实验

图的深度优先搜索I

代码长度限制 16 KB
时间限制 200 ms
内存限制 10 MB


题目描述

无向图 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。难点在于加了很多限制和要求。在合适的位置记录时间戳和遍历的边即可。

ps:之前建邻接表的方式很蠢,这次学到了使用起来比较方便的邻接表

代码

#include<iostream>
#include<vector>
#include<algorithm>
#define nMax 50005
using namespace std;
vector<int>ae[nMax];
struct edge{
    int x1,x2;
    edge(int X1,int X2):x1(X1),x2(X2){};
    edge(){};
};
vector<edge>sE;
int Time=0;
int d[nMax];
int f[nMax];
int vist[nMax];
void dfs(int now){
    d[now]=++Time;
    vist[now]=1;
    for(int i=0;i<ae[now].size();i++){
        int next=ae[now][i];
        if(!vist[next]){
            dfs(next);
            edge e(now,next);
            sE.push_back(e);
        }
    }
    f[now]=++Time;
}
bool cmp(edge e1,edge e2){
    return e1.x2<e2.x2;
}
int main(){
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=0;i<m;i++){
        int x1,x2;
        scanf(" %d %d",&x1,&x2);
        x1--;x2--;
        ae[x1].push_back(x2);
        ae[x2].push_back(x1);
    }
    //初始化
    for(int i=0;i<n;i++){
        vist[i]=0;
    }
    //
    for(int i=0;i<n;i++){
        if(!vist[i]){
            dfs(i);
        }
    }
    for(int i=0;i<n;i++){
        printf("%d %d\n",d[i],f[i]);
    }
    printf("%d",sE.size());
    sort(sE.begin(),sE.end(),cmp);
    for(int i=0;i<sE.size();i++){
        printf("\n%d %d",sE[i].x1+1,sE[i].x2+1);
    }
    return 0;
}

代码长度限制 16 KB
时间限制 500 ms
内存限制 5 MB


题目描述

二维平面上有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

思路

并查集。这个方法是O(n^2)的,但是能过好写。最初每个圆视为一个块,每两个圆检查一次是否相交,相交就合并两个圆所属的块,合并成功总块数减一,最后得到不相交的块数。

代码

#include<iostream>
#include <vector>
#include<math.h>
#define max 8005
#define ll long long
using namespace std;
struct circle{
    int x,y;
    ll r;
};
bool ifIntersect(circle o1,circle o2){
    ll d2=(o1.x-o2.x)*(o1.x-o2.x)+(o1.y-o2.y)*(o1.y-o2.y);
    ll lr2=(o1.r+o2.r)*(o1.r+o2.r);
    //printf("d%d %dd ",d2,lr2);
    return d2>lr2?0:1;
}
int f[max];
int vist[max];
int findF(int x){
    while(x!=f[x]){
        x=f[x];
    }
    return x;
}
int nasw;
void addEdge(int x1,int x2){
    if(!vist[x1]&&!vist[x2]){
        vist[x1]=vist[x2]=1;
        f[x1]=x2;
    }else if(!vist[x1]&&vist[x2]){
        vist[x1]=1;
        int f2= findF(x2);
        f[x1]=f2;
    }else if(vist[x1]&&!vist[x2]){
        vist[x2]=1;
        int f1= findF(x1);
        f[x2]=f1;
    }else if(vist[x1]&&vist[x2]){
        int f1= findF(x1);
        int f2= findF(x2);
        if(f1==f2)return;
        f[f1]=f2;
    }
    nasw--;//printf("%d:%d %d\n",nasw,x1+1,x2+1);
}
int main(){
    int n;
    scanf("%d",&n);
    vector<circle>ao;
    for(int i=0;i<n;i++){
        vist[i]=0;
        f[i]=i;
    }
    nasw=n;
    for(int i=0;i<n;i++){
        circle o0;
        scanf(" %d %d %d",&o0.x,&o0.y,&o0.r);
        for(int j=0;j<i;j++)
            if(ifIntersect(ao[j],o0)){
                addEdge(i,j);//printf("%d %d %d\n",ifIntersect(ao[j],o0),i+1,j+1);
            }
        ao.push_back(o0);
    }
    printf("%d",nasw);
    return 0;
}

供电

Code Size Limit 16 KB
Time Limit 500 ms
Memory Limit 10 MB


题目描述

要给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算法为基础,把建立供电站的代价视为一条虚边,该虚边的两端点是本地区Ni ——具有最小 建站代价 的地区N0(因为N0处必定会建站),该虚边的代价是Ni的 建站代价。然后将虚边与实边一视同仁,跑kruskal算法即可。这样若虚边存在图中,代表Ni处需要建站,若不存在,代表Ni处无需建站。这种做法有个额外的好处,每个地区至少与N0通过一条虚边连接,所以这张图一定是连通图。

二是以prim算法为基础,原本的prim算法一开始将所有节点的dis设置为无穷大,以便后续节点更新该值。此题将dis设置为该地区的建站代价,若被更新说明此地无需建站,若未被更新,则需建站。注意,若只是这样可能导致场上没有供电站,需要一开始就访问建站代价最小的地区N0,并从N0开始生成最小生成树。

顺便提一句,未用堆优化的prim时间为O(n^2),kurskal时间为O(eloge)。

另外,我因为过于依赖vector,导致最后一个样例一直超时,后来才直到数组的效率比vector至少要高几十倍。vector只是写起来方便,相对节省空间。

代码

下面给出kruskal算法的代码:

(我觉得我这个并查集的优化做的挺好。)

#include<iostream>
#include<algorithm>
#define max 10005
#define emax 60005
using namespace std;
struct edge{
    int x1,x2,l;
    edge(int X1,int X2,int L):x1(X1),x2(X2),l(L){}
    edge(){};
};
int f[max];
int findF(int x){
    while(f[x]!=x){
        x=f[x];
    }
    return x;
}
int vist[max];
int nasw=0;
int hSon[max];
void addEdge(int x1,int x2,int l){//printf("%d %d %d\n",x1,x2,l);
    if(x1==x2)return;
    if(!vist[x1]&&!vist[x2]){
        vist[x1]=1;vist[x2]=1;
        f[x1]=x2;
    }else if(!vist[x1]&&vist[x2]){
        vist[x1]=1;
        int f2= findF(x2);
        f[x1]=f2;
    }else if(vist[x1]&&!vist[x2]){
        vist[x2]=1;
        int f1= findF(x1);
        f[x2]=f1;
    }else if(vist[x1]&&vist[x2]){
        int f1= findF(x1);
        int f2= findF(x2);
        if(f1==f2)return;
        if(hSon[f1]==hSon[f2]){
            hSon[f1]++;
            f[f2]=f1;
        }else if(hSon[f1]<hSon[f2])f[f1]=f2;
        else if(hSon[f1]>hSon[f2])f[f2]=f1;
    }
    nasw+=l;
    return;
}
bool cmp(edge x1,edge x2){
    return x1.l<x2.l;
}
int main(){
    int N,M;
    scanf("%d %d",&N,&M);
    edge ae[emax];
    //添加虚边
    int al[max];
    int min=100000;
    int minI;
    for(int i=0;i<N;i++){
        scanf(" %d",&al[i]);
        if(min>=al[i]){
            min=al[i];
            minI=i;
        }
    }
    for(int i=0;i<N;i++){
        if(i==minI)continue;
        edge e(minI,i,al[i]);
        ae[i]=e;
    }
    //添加边
    for(int i=0;i<M;i++){
        edge e(0,0,0);
        scanf(" %d %d %d",&e.x2,&e.x1,&e.l);
        e.x1--;e.x2--;
        ae[N+i]=e;
    }
    //排序
    sort(ae,ae+N+M,cmp);
    /*while(!ae.empty()){
        edge e=ae.front();
        ae.erase(ae.begin());
        printf("%d %d %d\n",e.x1+1,e.x2+1,e.l);
    }*/
    //初始化
    for(int i=0;i<N;i++){
        f[i]=i;
        vist[i]=0;
        hSon[i]=0;
    }
    for(int i=0;i<N+M;i++){
        edge e=ae[i];
        addEdge(e.x1,e.x2,e.l);
    }
    nasw+=min;
    printf("%d",nasw);
    return 0;
}

发红包

Code Size Limit 16 KB
Time Limit 50 ms
Memory Limit 10 MB


题目描述

新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,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视为一条有向边:c2->c1,记录每个结点的入度。每次访问入度为0的结点c0(因为c0没有要求,不需要比任何人小)接下来访问c0指向的每个结点ci,更新ci的红包,减少ci的入度(相当于减少了ci的一个要求,当ci所有要求都被满足,那么就可以访问他了)。因为ci提出要比cki小,假设所有cki中最大的红包是ck0的,ci的红包只需比ck0大一块钱即可。

代码

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
struct edge{
    int to,next;
};
using namespace std;
int*du;
int*mny;
int nvist=0;
vector<edge>ae;
int*map;
queue<int>que;
void addEdge(int from,int to){
    edge e;
    e.to=to;
    e.next=map[from];
    map[from]=ae.size();
    ae.push_back(e);
}
int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    //初始化
    du = new int[n];
    mny = new int[n];
    map = new int[n];
    for (int i = 0; i < n; i++) {
        du[i] = 0;
        mny[i] = 0;
        map[i] = -1;
    }
    //建立邻接表
    for (int i = 0; i < m; i++) {
        int from, to;
        scanf(" %d %d", &to, &from);
        to--;from--;
        du[to]++;
        addEdge(from, to);
    }
    //所有度为0的入队
    for(int i=0;i<n;i++){
        if(du[i]==0){
            que.push(i);//printf("%d\n",i);
            nvist++;
        }
    }
    //访问结点
    while(!que.empty()){
        int now=que.front();
        que.pop();
        int nextE=map[now];
        while(nextE!=-1){
            int next=ae[nextE].to;
            du[next]--;
            if(du[next]==0){
                que.push(next);
                nvist++;
            }
            if(mny[next]<mny[now]+1){
                mny[next]=mny[now]+1;
            }
            nextE=ae[nextE].next;
        }
    }
    //无法满足
    if(nvist!=n){
        printf("-1");
        return 0;
    }
    //输出
    int N=0;
    for(int i=0;i<n;i++){
        N+=mny[i];
    }
    N+=888*n;
    printf("%d",N);
}
posted @ 2021-06-07 23:13  荒于曦  阅读(163)  评论(0)    收藏  举报