Y1课程笔记:第六课时(欧拉路与拓扑问题)

知识点一:欧拉路

概念梳理

欧拉路径:连通图中,有一路径经过每条边一次且仅有一次

欧拉回路:欧拉路径基础上要回起点

欧拉图:有欧拉回路的图

判断方法

图连通

无向图:

1.欧拉路径:奇点有0或2个

2.欧拉回路:奇点有0个

有向图

1,所有点出度入度都相等或除了起点所有点出度入度都相等,起点出度=入度+1,终点入度=出度+1

2,所有点出度入度都相等

模板题二则

对于边:欧拉路

时间限制:1秒        内存限制:128M

题目描述

      有一个图,图中要么有两个奇点要么0奇点,如果是欧拉回路请从第一个点为起点开始遍历,如果有两个奇点,则以字典序小的为起点开始遍历,在遍历的过程中,字典序小的先遍历。

输入描述

第一行两个整数,n和e,表示有n个节点,e条边,n<50.

输出描述

只有一行,为欧拉路或欧拉回路。

样例

输入

5 5
1 2
2 3
3 4
4 5
5 1

输出

1 2 3 4 5 1

解题思路详见代码注释,没什么好说的,上代码

#include<bits/stdc++.h>
using namespace std;
int n,m,g[55][55],du[55],ans[55],top;
void dfs(int x){
    for(int i=1;i<=m;i++){
        if(g[x][i]){
            g[x][i]--;
            g[i][x]--;
            dfs(i);
        }
    }
    ans[++top]=x;//第一次有可能搜不到,回溯时记录
}
int main(){
    cin>>n>>m;
    int a,b;
    for(int i=1;i<=m;i++){
        cin>>a>>b;
        g[a][b]++;
        g[b][a]++;
        du[a]++;
        du[b]++;
    }
    int t=1;
    for(int i=1;i<=n;i++){
        if(du[i]%2==1){//记录最小的奇点
            t=i;
            break;
        }
    }
    dfs(t);//深搜记录答案
    while(top){
        cout<<ans[top]<<' ';
        top--;
    }
}

对于点:

地下通道迷宫

时间限制:1秒        内存限制:32M

题目描述

小明最近看了著名电影《地道战》,从此对地道产生了浓厚的兴趣。地道战是在抗日战争时期,在华北平原上抗日军民利用地道打击日本侵略者的作战方式。地道网是房连房、街连街、村连村的地下工事,如下图所示。

小明很想体验一下在地道中穿梭的感觉,于是他来到了一处供人们娱乐的地下通道迷宫。已知它的通道都是直的,而通道所有交叉点(包括通道的端点)上都有一盏灯和一个开关。请你编程告诉小明如何从某个起点开始在迷宫中点亮所有的灯并回到起点?
一个地下通道点灯迷宫及其对应的图如下。

入描述

输入包含多组测试数据,每组输入的第1行给出三个正整数,分别表示地下迷宫的节点数N(1<N<=1000,表示通道所有交叉点和端点)、边数M(<=3000,表示通道数)和探索起始节点编号S(节点从1到N编号)。随后的M行对应M条边(通道),每行给出一对正整数,分别是该条边直接连通的两个节点的编号。

输出描述

对于每组输入,若可以点亮所有节点的灯,则输出从S开始并以S结束的包含所有节点的序列,序列中相邻的节点一定有边(通道);否则虽然不能点亮所有节点的灯,但还是输出点亮部分灯的节点序列,最后输出0,此时表示迷宫不是连通图。

为了使得输出具有唯一的结果,我们约定以节点小编号优先的次序访问(点灯)。在点亮所有可以点亮的灯后,以原路返回的方式回到起点。

样例

输入

6 8 1
1 2
2 3
3 4
4 5
5 6
6 4
3 6
1 5
6 6 6
1 2
1 3
2 3
5 4
6 5
6 4

输出

1 2 3 4 5 6 5 4 3 2 1
6 4 5 4 6 0

解题思路详见代码注释,,also没什么好说的,上代码,too

#include<bits/stdc++.h>
using namespace std;
int t,n,m,vis[1005],g[1005][1005],du[1005],ans[1005],top,anss[1005];
void dfs(int x){
    cout<<x<<' ';
    vis[x]=1;
    for(int i=1;i<=n;i++){
        if(vis[i]==0&&g[x][i]){
            dfs(i);
            cout<<x<<' ';//记录搜索时答案的
        }
    }
    ans[++top]=x;
}
int main(){
    while(cin>>n>>m>>t){
        memset(vis,0,sizeof vis);
        memset(g,0,sizeof g);
        top=0;
    int a,b;
    for(int i=1;i<=m;i++){
        cin>>a>>b;
        g[a][b]++;
        g[b][a]++;
    }
    vis[t]=1;
    dfs(t);
    int cnt=0;
    int topp=top;
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            cout<<0;//反向再输出一遍
            break;
        }
    }
    cout<<endl;
    }
}

习题

欧拉回路

时间限制:1秒        内存限制:128M

题目描述
题目描述

有一天一位灵魂画师画了一张图,现在要你找出欧拉回路,即在图中找一个环使得每条边都在环上出现恰好一次。

一共两个子任务:

1. 这张图是无向图。
2. 这张图是有向图。

输入描述

第一行一个整数 t,表示子任务编号。t∈1,2,如果 t=1 则表示处理无向图的情况,如果 t=2 则表示处理有向图的情况。

第二行两个整数 n,m,表示图的结点数和边数。

接下来 m 行中,第 i 行两个整数 vi,ui,表示第 i 条边(从 11 开始编号)。保证 1≤vi,ui≤n。

1. 如果 t=1 则表示 vi 到 ui 有一条无向边。
2. 如果 t=2 则表示vi 到 ui 有一条有向边。

图中可能有重边也可能有自环。

输出描述

如果不可以一笔画,输出一行 “NO”。

否则,输出一行 “YES”,接下来一行输出一组方案。

1. 如果 t=1,输出 m 个整数 p1,p2,…pm。令 e=∣pi∣,那么 e 表示经过的第 i 条边的编号。如果 pi 为正数表示从 ve 走到 ue,否则表示从 ue 走到 ve。
2. 如果 t=2,输出 m 个整数 p1,p2,…,pm。其中 pi 表示经过的第 i 条边的编号。

输入样例1

  1. 1
  2. 3 3
  3. 1 2
  4. 2 3
  5. 1 3
输出样例1
  1. YES
  2. 1 2 -3
输入样例2
  1. 2
  2. 5 6
  3. 2 3
  4. 2 5
  5. 3 4
  6. 1 2
  7. 4 2
  8. 5 1
输出样例2
  1. YES
  2. 4 1 3 5 2 6
数据范围

全部数据:1≤n≤10​5​​,0≤m≤2×10​5​​

解题思路

此题与前几题比较相像,但数据范围达到了1e5,需要用链式前向星存图,并且题目中有一项操作比较特殊:如果 t=1,输出 m 个整数 p1,p2,…pm。令 e=∣pi∣,那么 e 表示经过的第 i 条边的编号。如果 pi 为正数表示从 ve 走到 ue,否则表示从 ue 走到 ve。

对于此要求,我们尝试从链式前向星存储方式入手

由于我们从0开始存储,所以下标为奇数的是反向边,下标/2+1就是边的编号。由此题目就简单多了,我们只需遍历点,存储边即可。

AC代码

#include<bits/stdc++.h>
using namespace std;
int ver[200005],
    ne[200005],
    h[200005],ans[200005],v[200005],top,t,n,m,tot=-1,din[200005],dout[200005];
void dfs(int x){
    for(int i=h[x];~i;i=ne[i]) {
        if(v[i]){
            continue;
        }
        v[i]=1;
        if(t==1){
            v[i^1]=1;
        }
        int tmp;
        if(t==1){
            tmp=i/2+1;
            if(i&1){
                tmp=-tmp;
            }
        }
        else{
            tmp=i+1;
        }
        dfs(ver[i]);
        ans[++top]=tmp;
    }
    
}
void add(int a, int b) {
    tot++;
    ver[tot]=b;
    ne[tot]=h[a];
    h[a]=tot;
}                                                   
int main(){
    memset(h,-1,sizeof h);
    cin>>t;
    cin>>n>>m;
    int a,b;
    for(int i=1;i<=m;i++){
        cin>>a>>b;
        if(t==1){
        add(a,b);
        add(b,a);
        }
        else{
            add(a,b);
        }
        din[b]++;
        dout[a]++;
    }
    if(t==1){
        for(int i=1;i<=n;i++){
            if((din[i]+dout[i])&1){
                cout<<"NO";
                return 0;
            }
        }
    }
    else{
        for(int i=1;i<=n;i++){
            if(din[i]!=dout[i]){
                cout<<"NO";
                return 0;
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(h[i]!=-1){
            dfs(i);
            break;
        }
    }
    if(top<m){
        cout<<"NO";
        return 0;
    }
    else{
        cout<<"YES"<<endl;
        for(int i=top;i>=1;i--){
            cout<<ans[i]<<' ';
        }
    }
    return 0;
}

此处有几道并查集问题,之后来填坑

知识点二:拓扑问题

知识补充

DAG图 有向无环图,即无环的有向图,在此图中,无法从一个点出发,经过若干条边重新回到起点。 有向无环图也称为DAG(Directed  Acycline  Graph)图。

AOV网 用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网 ,简称 AOV (Activity On Vertex network) 网。 例如:学习之间的一些关系,你影响先学习C++语言,然后才能开始学习数据结构与算法。 若从 i 到 j 有一条有向路径,则 i是 j 的前驱;j 是 i 的后继。  若 < i , j > 是网中有向边,则 i 是  j 的直接前驱; j 是 i 的直接后继。 AOV 网中不允许有回路,因为如果有回路存在,则表明某项活动以自己为先决条件,显然这是荒谬的。

拓扑排序 在 AOV 网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若 AOV 网中有弧 <i,  j>存在,则在这个序列中, i  一定排在  j 的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。 且该序列必须满足下面两个条件: 1、每个顶点出现且只出现一次。 2、有弧 <i,  j>存在,则在这个序列中,i 一定排在  j 的前面。 注: 拓扑排序针对的是有向无环图(DAG) 十一、如何在一个有向图中找到拓扑排序 首先在一个有向图中找到一个入度为0的点(无前驱),然后输出。 然后删除此点以及此点的所有出边。 不断重复这两个步骤,直至全部顶点都输出或者出现了环。

如上图所示的拓扑排序得到的序列应该是:a,b,c,e,d,g,i,j,f,k,l,h 考虑:一个图的拓扑序列一定唯一吗? 答案是不唯一的,i,k,l,j,a,b,c,e,d,g,f,h也是其一个拓扑排序序列。

模板题

 是否合法

时间限制:1秒        内存限制:128M

题目描述

某学习圈中,当有人提出疑问时,许多热情的人会来帮助您。然后,被帮助的那个人将称帮他的人为“师傅”,而提供帮助的人将拥有一个不错的“徒弟”。渐渐地,有许多对“师傅和徒弟”。但是随后出现了问题:师傅太多了,徒弟也太多了,我们怎么知道它是否合法?我们都知道,一个师傅可以有很多徒弟,而一个徒弟可能也可以有很多师傅,这是合法的。但是,有些人并不那么诚实,他们有非法关系。以小可和小达为例,小可是小达的师傅,同时小达是小可的师傅,这是非法的!为避免这种情况,请帮助我们判断他们的关系是否合法。请注意,“师徒”关系是可传递的。这意味着如果A是B的师傅,而B是C的师傅,则A是C的师傅。

输入描述

输入包含几个测试用例。对于每种情况,第一行包含两个整数,N(要测试的成员)和M(要测试的关系)(2 <= N,M <= 100)。

然后是M行,每行包含一对(x,y),这意味着x是y的师傅,而y是x的徒弟。
输入以N = 0结束。为简单起见,我们给每个人一个数字(0,1,2,...,N-1)。我们使用他们的数字而不是他们的名字。

输出描述

对于每个测试用例,如果合法,则输出“YES”,否则输出“NO”。

样例

输入

3 2
0 1
1 2
2 2
0 1
1 0
0 0

输出

YES
NO

解题思路

可以把师徒关系转化成图,关系不合法的条件则为图中有环。

操作:如果某个人入度为零(无师傅),则删除此人,将其徒弟入度-1,

循环操作直到无人可删,如果删除人数小于总人数则表明出现了环,关系不合法,反之亦然。

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,g[105][105],dis[105],cnt,du[105];
queue<int>q;
vector<int>v[105],v1[105];
int main(){
    while(cin>>n>>m){
        for(int i=1;i<=105;i++){
            v[i].clear();
        }
        while(!q.empty()){
            q.pop();
        }
        if(n==0){
            return 0;
        }
        memset(dis,0,sizeof dis);
        memset(du,0,sizeof du);
        cnt=0;
         memset(g,0,sizeof g);
    for(int i=1;i<=m;i++){
        cin>>x>>y;
        v[x].push_back(y);
        du[y]++;
    }
    for(int i=0;i<n;i++){
        if(du[i]==0){
                q.push(i);
                //dis[i]=1;
            }
    }
    while(!q.empty()){
        int x=q.front();
        q.pop();
        cnt++;
        for(int i=0;i<v[x].size();i++){
            du[v[x][i]]--;
            if(du[v[x][i]]==0){
                q.push(v[x][i]);
            }
        }
        
    }
    if(cnt<n){
        cout<<"NO";
    }
    else{
        cout<<"YES";
    }
    cout<<endl;
    }
}

习题

信息传递

时间限制:1秒        内存限制:128M

题目描述

有 n 个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?

输入描述

共2行。
第1行包含1个正整数 n ,表示 n 个人。
第2行包含 n 个用空格隔开的正整数 T1,T2,⋯⋯,Tn,其中第 i 个整数 Ti 表示编号为 i 的同学的信息传递对象是编号为 Ti 的同学, Ti≤n 且 Ti≠i。

输出描述

1个整数,表示游戏一共可以进行多少轮。

样例

输入

5
2 4 2 3 1

输出

3

提示

对于 30%的数据, n ≤ 200;
对于 60%的数据,n ≤ 2500;
对于 100%的数据,n ≤ 200000。

解题思路

当一个同学听说自己的信息,必定是图上出现了环,由此此题目与前几题几乎一样,不过要同时记录有向图以及无向图,在bfs之后再dfs遍历环上的点,寻找最小的环并输出长度。

AC代码

#include<bits/stdc++.h>
using namespace std;
int tmp,n,m,vis[200005],du[200005];
queue<int>q; 
vector<int>v1[200005],v[200005];
void dfs(int x){
    //cout<<'d'<<x<<endl;
    tmp++;
    for(int i=0;i<v1[x].size();i++){
        if(vis[v1[x][i]]==0){
            vis[v1[x][i]]=1;
            dfs(v1[x][i]);
        }
    }
}
int main(){
    cin>>n;
    int x,cnt=0;
    for(int i=1;i<=n;i++){
        cin>>x;
        v[i].push_back(x);
        v1[i].push_back(x);
        v1[x].push_back(i);
        du[x]++;
    }
    for(int i=1;i<=n;i++){
        if(du[i]==0){
            //cout<<'p'<<i<<endl;
            q.push(i);
        }
    }
    while(!q.empty()){
        int x=q.front();
        //cout<<'s'<<x<<endl;
        vis[x]=1;
        q.pop();
        cnt++;
        for(int i=0;i<v[x].size();i++){
            du[v[x][i]]--;
            if(du[v[x][i]]==0){
                q.push(v[x][i]);
            }
        }
    }
    int ans=0x3f3f3f3f;
    for(int i=1;i<=n;i++){
        if(vis[i]==0){
            tmp=0;
            vis[i]=1;
            //cout<<'i'<<i<<endl;
            dfs(i);
            ans=min(tmp,ans);
        }
    }
    cout<<ans;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值