欧拉路和欧拉回路

本文介绍了欧拉路和欧拉回路的概念,包括它们在无向图和有向图中的存在条件。欧拉路是每条边恰好通过一次的路径,而欧拉回路则是起点和终点相同的欧拉路。对于无向图,存在欧拉路的充要条件是图是连通的,且所有点的度数为偶数或仅有2个点的度为奇数。在有向图中,每个点的入度等于出度则存在欧拉回路。文章还提供了相关例题和解题思路,涉及字典树、单词接龙和树的最短欧拉路问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

欧拉路:欧拉路是指从图中任意一个点开始到图中任意一个点结束的路径,并且图中每条边通过的且只通过一次

欧拉回路:欧拉回路是指起点和终点相同的欧拉路

无向图:

无向图存在欧拉路的充要条件:

  • 图是连通的
  • 所以点的度数位偶数,或者有且仅有2个点的度为奇数

如果所以点的度数都为偶数,那么就是欧拉回路,任意点都可以为起点

如果存在度数为奇数的点,那么呢两个奇数入度点为起点和终点

有向图:

  • 每个点的入度等于出度,则存在欧拉回路(任意一点有度的点都可以作为起点)
  • 除两点外,所有入度等于出度。这两点中一点的出度比入度大,另一点的出度比入度小,则存在欧拉路。取出度大者为起点,入度大者为终点。

如果不用输出路径,一般用并查集判断是否连通

如果要输出路径,一般用深搜找路径和判断连通,cnt==n,即连通

 

例题:

字典树+欧拉图(poj-2513):https://vjudge.net/problem/POJ-2513

题意:

有若干个棍子,棍头用字母表示,相同的棍头可以连接在一起,问能否把所有棍子连在一起?

解析:

把颜色转化为点,然后判断是否连通,是否入度全为偶或者只有2个奇

ac:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define MAXN 1000005
using namespace std;
char a[13],b[13];
int trie[MAXN][28];
int vis[MAXN];
int in[MAXN];
int cnt=0;
int f[MAXN];
int init()
{
    for(int i=0;i<MAXN;i++)
        f[i]=i;
}
int get(int x)
{
    return x==f[x]?x:f[x]=get(f[x]);
}
int unite(int x,int y)
{
    f[get(x)]=get(y);
}

void ins(char s[])
{
    int rt=0;//根结点
    int len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int c=s[i]-'a';
        if(trie[rt][c]==0)
            trie[rt][c]=++cnt;
        rt=trie[rt][c];
    }
    vis[rt]=cnt;
}

int getid(char s[])
{
    int rt=0;
    int len=strlen(s);
    for(int i=0;i<len;i++)
    {
        int c=s[i]-'a';
        if(trie[rt][c]==0)
            return 0;
        rt=trie[rt][c];
    }
    return vis[rt];
}

int main()
{
    init();
    int st;
    while(scanf("%s%s",&a,&b)!=EOF)
    {
        int aa=getid(a);
        if(aa==0)
        {
            ins(a);
            aa=getid(a);
        }
        int bb=getid(b);
        if(bb==0)
        {
            ins(b);
            bb=getid(b);
        }
        in[aa]++;
        in[bb]++;
        st=aa;
        unite(aa,bb);
    }
    int num=0,sign=0;
    st=get(st);
    for(int i=1;i<=cnt;i++)
    {
        if(in[i]==0)
            continue;
        if(in[i]%2==1)
            num++;
        if(get(i)!=st)
        {
            sign=1;
            break;
        }
    }
    if(sign==0&&(num==0||num==2))
        printf("Possible\n");
    else
        printf("Impossible\n");
    return 0;
}

有向图字典序输出欧拉路径:https://vjudge.net/problem/POJ-2337

题意:

单词接龙,如果能接龙,输出字典序最小的单词接龙

解析:

有向图的欧拉路径判断

1.所以点入度=出度,或者存在2个点,1个点入度=出度+1,一个点出度=入度+1(起点)

2.用dfs搜路径,用回溯法存路径,倒序输出

怎么建图保证是字典序最小呢?

我们用链式前向星建边,因为我们dfs回溯输出存路径,然后倒序输出路径,所以我们要从大到小建边

ctr[i]倒序排序,然后建边,判断是否有欧拉路,然后搜索欧拉路,存路径,倒序输出

ac:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define MAXN 2005
using namespace std;
string ctr[MAXN];
int to[MAXN<<2],nxt[MAXN<<2],val[MAXN<<2],head[MAXN<<2];
int in[MAXN<<2],out[MAXN<<2];
int tot=0;
void add(int u,int v,int w)
{
    to[++tot]=v;
    nxt[tot]=head[u];
    val[tot]=w;
    head[u]=tot;
}

int vis[MAXN<<2];
int ans[MAXN<<2];
int num=0;

void dfs(int x)
{
    for(int i=head[x];i;i=nxt[i])
    {
        int v=to[i];
        if(vis[i]==0)
        {
            vis[i]=1;
            dfs(v);
            ans[++num]=val[i];
        }
    }
}

void init()
{
    memset(head,0,sizeof(head));
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    memset(vis,0,sizeof(vis));
    num=tot=0;
}

int main()
{
    int t,n;
    cin>>t;
    while(t--)
    {
        init();
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>ctr[i];
        sort(ctr+1,ctr+n+1);//排序
        for(int i=n;i>=1;i--)
        {
            int aa=ctr[i][0]-'a'+1;
            int bb=ctr[i][ctr[i].length()-1]-'a'+1;
            in[bb]++;
            out[aa]++;
            add(aa,bb,i);
        }
        int start=0,ed=0,sign=0;
        for(int i=1;i<=26;i++)//起点和终点至多1个
        {
            if(out[i]-in[i]==1)//起点
            {
                if(start==0)
                    start=i;
                else{
                    sign=1;
                    break;
                }
            }
            else if(in[i]-out[i]==1)
            {
                if(ed==0)
                    ed=i;
                else{
                    sign=1;
                    break;
                }
            }
            else if(in[i]!=out[i])
            {
                sign=1;
                break;
            }
        }
        if(sign==0)
        {
            if(start==0)//为欧拉回路
            {
                for(int i=1;i<=26;i++)
                    if(in[i]!=0||out[i]!=0)
                    {
                        start=i;
                        break;
                    }
            }
            dfs(start);
            if(num!=n)
                cout<<"***"<<endl;
            else{
                for(int i=num;i>=1;i--)
                {
                    cout<<ctr[ans[i]];
                    if(i!=1)
                        cout<<".";
                    else
                        cout<<endl;
                }
            }
        }
        else{
            cout<<"***"<<endl;
        }
    }
    return 0;
}

https://vjudge.net/problem/HDU-3018

题意:给出一个图,问几笔画才能经过所有边(这题对于单独点,不作计算)

解析:对于每一个连通块,分别求其欧拉路数目

如果该连体块是欧拉路,1笔画

如果不上欧拉路,欧拉路为奇数度/2

ac:

#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int f[MAXN];
int vis[MAXN];
int in[MAXN];
vector<int> vc[MAXN];

void init()
{
    for(int i=0;i<MAXN;i++)
        f[i]=i,vc[i].clear();
    memset(vis,0,sizeof(vis));
    memset(in,0,sizeof(in));
}

int get(int x)
{
    if(x!=f[x])
        f[x]=get(f[x]);
    return f[x];
}

void unite(int x,int y)
{
    f[get(x)]=get(y);
}

int main()
{
    int n,m,a,b;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            unite(a,b);
            in[a]++;
            in[b]++;
        }
        for(int i=1;i<=n;i++)
        {
            if(in[i]==0)
                continue;
            int x=get(i);
            vis[x]=1;
            vc[x].push_back(i);
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            if(vis[i]==1)
            {
                int sign=0;
                for(int j=0;j<vc[i].size();j++)
                {
                    int x=vc[i][j];
                    if(in[x]%2==1)
                        sign++;
                }
                if(sign==0||sign==2)
                    ans++;
                else{
                    ans+=sign/2;
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

https://ac.nowcoder.com/acm/contest/369/C

题意:

求一棵树的最短欧拉路之和

解析:

结论:最短欧拉路=2*树的所有边长和-树的直径

求树的直径:,先从任意一点出发,求其最长路径a,然后从最长路径端点出发,再求最长路径b,b就是该树的最长路径.

ac:

#include<bits/stdc++.h>
#define MAXN 200005
#define ll long long
using namespace std;

int to[MAXN<<1],val[MAXN<<1],head[MAXN<<1],nxt[MAXN<<1];
int tot=0;
ll maxs=0,st=-1;

void init()
{
    maxs=0,st=-1;
    tot=0;
    memset(head,0,sizeof(head));
}

void add(int u,int v,int w)
{
    to[++tot]=v;
    val[tot]=w;
    nxt[tot]=head[u];
    head[u]=tot;
}

void dfs(int u,int fa,ll sum)
{
    if(sum>maxs)
        maxs=sum,st=u;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa)
            continue;
        dfs(v,u,sum+val[i]);
    }
}

int main()
{
    init();
    int n,a,b,w;
    ll sum=0;
    scanf("%d",&n);
    for(ll i=0;i<n-1;i++)
    {
        scanf("%d%d%d",&a,&b,&w);
        sum+=w;
        add(a,b,w);
        add(b,a,w);
    }
    dfs(1,0,0);
    dfs(st,0,0);
    printf("%lld\n",sum*2-maxs);
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值