AcWing 算法提高课 欧拉回路和欧拉路径

定义:经过每一条边且每一条边恰好只经过一次

一、无向图中,当所有边都连通时:

存在欧拉路径,等价于,图中度为奇数的点只有0或2个。

存在欧拉回路,等价于,图中度为奇数的点只有0个。

二、有向图中,当所有边都连通时:

存在欧拉路径,充要条件,要么所有点的出度等于入度,要么除了两个点之外,其余所有点的出度等于入度,且剩余两个点,一个满足出度比入度多1(起点),另一个入度比出度多1(终点)。

存在欧拉回路,充要条件,所有点的出度等于入度。

 

模板:

欧拉回路模板:(记录边)

const int N=100010;
const int M=400010;
vector<int> adj[N];
vector<int> id[N];
int idx;
int n,m;
bool used[M];
int ans[M],cnt;

int din[N],dout[N];

void DFS1(int u)
{
    //注意由于可能存在环,adj[u]可能被改变
    //故不能使用for(int i=adj[u].size()-1;i>=0;i--)这样的循环
    while(adj[u].size())
    {
        int eid=id[u].back();
        int nxt=adj[u].back();
        if(used[eid])//已经被其反向边遍历过
        {
            adj[u].pop_back();
            id[u].pop_back();
            continue;
        }
        
        adj[u].pop_back();
        id[u].pop_back();
        used[eid^1]=true;//无向图标记反向边
        
        DFS1(nxt);
        
        int t=eid/2+1;//由于无向图建了两条边,故需要获取边的真实编号
        if(eid&1) ans[++cnt]=-t;//反向边
        else ans[++cnt]=t;//正向边
        
    }
}

void DFS2(int u)
{
    //注意由于可能存在环,adj[u]可能被改变
    //故不能使用for(int i=adj[u].size()-1;i>=0;i--)这样的循环
    while(adj[u].size())
    {
        int eid=id[u].back();
        int nxt=adj[u].back();
        adj[u].pop_back();
        id[u].pop_back();
        DFS2(nxt);
        ans[++cnt]=eid+1;
    }
}

void YD()
{
    int t;cin>>t;
    cin>>n>>m;
    fore(i,1,n)
    {
        adj[i].clear();
        id[i].clear();
    }
    memset(din,0,sizeof(din));
    memset(dout,0,sizeof(dout));

    if(t==1)//无向图
    {
        idx=0;
        fore(i,1,m)
        {
            int a,b;cin>>a>>b;
            adj[a].pub(b);
            id[a].pub(idx++);
            adj[b].pub(a);
            id[b].pub(idx++);
            din[b]++;
            dout[a]++;
        }
        fore(i,1,n)
        {
            if((din[i]+dout[i])%2)
            {
                cout<<"NO"<<endl;
                return;
            }
        }
        fore(i,1,n)
        {
            if(adj[i].size())//找到有边相连的一个点作为起点
            {
                DFS1(i);
                break;
            }
        }
        if(cnt<m)//图不是连通的
        {
            cout<<"NO"<<endl;
            return;
        }
        cout<<"YES"<<endl;
        for(int i=cnt;i>0;i--)//由于深搜,所以最后一个点是起点
        {
            cout<<ans[i]<<' ';
        }
        cout<<endl;
    }
    else if(t==2)//有向图
    {
        fore(i,1,m)
        {
            int a,b;cin>>a>>b;
            adj[a].pub(b);
            id[a].pub(idx++);
            din[b]++;
            dout[a]++;
        }
        fore(i,1,n)
        {
            if(din[i]!=dout[i])
            {
                cout<<"NO"<<endl;
                return;
            }
        }
        fore(i,1,n)
        {
            if(adj[i].size())//找到有边相连的一个点作为起点
            {
                DFS2(i);
                break;
            }
        }
        if(cnt<m)//图不是连通的
        {
            cout<<"NO"<<endl;
            return;
        }
        cout<<"YES"<<endl;
        for(int i=cnt;i>0;i--)//由于深搜,所以最后一个点是起点
        {
            cout<<ans[i]<<' ';
        }
        cout<<endl;
    }
    

}
View Code

t==1为无向图,t==2为有向图

例题:https://www.acwing.com/problem/content/1186/

注意:

1、欧拉回路问题在dfs中,需要用边来判重,如果每次都遍历所有边,最差需要O(m^2),所以需要优化,比如每次遍历到一个边就把它删掉。

2、对于无向图,由于会建立两条边,故遍历时也需要删去其反向边。由于正反边的输入次序(标号)仅差1,可以采用标记法处理:建立边标记数组,在邻接表中插入边时,也记录边的标号,然后再删除标号为idx的边时,也将标号为idx^1的边进行标记。

 

欧拉路径模板:DFS和欧拉回路模板一样,判断是否存在以及起点的方式不同

无向图欧拉路径模板:(记录点,且按模板排序后为最小字典序)

const int N=510;
const int M=1100;
int ans[M];
vector<PII> adj[N];
vector<int> id[N];
int idx=0;

int cnt=0;
bool used[2*M];
int din[N];
int dout[N];
void DFS(int u)
{
    while(adj[u].size())
    {
        auto [nxt,eid]=adj[u].back();
        if(used[eid])
        {
            adj[u].pop_back();
            continue;
        }
        adj[u].pop_back();
        used[eid^1]=true;
        DFS(nxt);
    }
    ans[++cnt]=u;
}
void YD()
{
    int m;cin>>m;
    fore(i,1,m)
    {
        int a,b;cin>>a>>b;
        adj[a].push_back({b,idx++});
        adj[b].push_back({a,idx++});
        din[a]++;
        dout[b]++;
        
    }
    fore(i,1,N)
    {
        sort(adj[i].begin(),adj[i].end(),greater<PII>());
    }
    bool flag=false;
    fore(i,1,N)
    {
        if((din[i]+dout[i])%2)//是否有固定的起点
        {
            flag=true;
            DFS(i);
            break;
        }
    }
    if(!flag)
    {
        DFS(1);
    }
    rofe(i,cnt,1)
    {
        cout<<ans[i]<<endl;
    }
}
View Code

例题:https://www.acwing.com/problem/content/1126/

posted @ 2022-09-21 19:52  80k  阅读(22)  评论(0编辑  收藏  举报