第六次CCF软件能力认证D题-送货 题型:欧拉路径和欧拉回路模板题

81 阅读3分钟

3225. 送货 - AcWing题库

首先我们判断本题是否有解。

我们从欧拉路径的定义出发,看本题是否有解。

对于一个无向图来说,

满足它存在欧拉回路的充要条件是:

图像必须是连通图。所有点的度数必须是偶数。

满足它存在欧拉路径的充要条件是:

图像必须是连通图。

度数为奇数的点, 要么有0个,要么有2个。

对于一个有向图来说:

满足它存在欧拉回路的充要条件是:

图像必须是连通图所有节点的出度等于入度

满足它存在欧拉路径的充要条件是:

图像必须是连通图

要么满足节点的出度和入度个数一样,要么满足节点的入个数比出度个数多1,要么满足节点出度个数比入度个数多1。

如果不满足上面的条件,则本题无解。如果本题有解,那么就进行爆搜,哪条边没有被遍历过就走哪条边,且一条边只走一次。

这样的复杂度是O(M2)O(M^2)的。因为我们刚开始从第一条边开始搜,搜完之后第一条边就被遍历过了,就不能再走了。假设当我们走完第二条边又走到第一条边的时候,这时候就需要把第一条边跳过。当我们走第三条边的时候又走到第一条边第二条边的时候就需要把前两条边都跳过,假设我们遍历了m条边,最终需要跳过m条边,时间复杂度就是O(M2)O(M^2)

因此,我们可以进行优化,每走完一条边直接把这条边删掉,这样在后续遍历中就不会再遇到这条边,就不用再跳过了。我们用一个链表,每次走完一条边就把指针指向下一条边,这样就把当前遍历过的边删掉了。但是链表不好写,这里我们可以用set来写,我们把所有的边放到set里面,每用掉一条边就从set里把用过的边删掉。

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=1e5+10;
int p[N],idx;
int n,m;
set<int>g[N];
int ans[M],top;

int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}

void dfs(int u)
{
    while(g[u].size())
    {
        int t=*g[u].begin();
        g[u].erase(t),g[t].erase(u);   //因为是无向边,所以要从t里删除u,从u里面删除t
        dfs(t);
    }
    ans[++top]=u;  //回溯
}
int main()
{
    cin>>n>>m;
    
    //判断是否连通
    for(int i=1;i<=n;i++)p[i]=i;  //初始话父亲数组
    
    while(m--)
    {
        int a,b;cin>>a>>b;
        g[a].insert(b),g[b].insert(a);    //无向边,两边都要插入
        p[find(a)]=find(b);  //合并
    }
    
    //判断一下是否有解
    int s=0;
    for(int i=1;i<=n;i++)
    {
        if(find(i)!=find(1))//如果不连通
        {
            puts("-1");
            return 0;  //结束程序
        }
        else if(g[i].size()%2) s++;  //如果点的度数是奇数,那么就是奇点,统计一下奇点的个数
    }
    
    
    //如果奇点个数不是0或者2  或者节点个数是2但是起点度数不是奇点,那么也是无解
    if(s!=0&&s!=2||s==2&&g[1].size()%2==0)
    {
        puts("-1");
        return 0;
    }
    
    dfs(1);  //从起点开始爆搜
    for(int i=top;i;i--)
    {
        cout<<ans[i]<<" ";
    }
    return 0;
}

image.png