欧拉回路和欧拉路径

在一张图中,从一个点出发每条边经过且只经过一次得到的路径,如果最后回到起点,那么就是欧拉回路,如果最后没有回到起点,那么得到的就是欧拉路径。

在无向图中,欧拉路径满足的要求是,除了起点和终点以外其他点的度数都是偶数;欧拉回路满足的要求是所有点的度数都是偶数。

在有向图中,欧拉路径满足的要求是:除了起点和终点外,其他点的出度等于入度,起点的出度比入度多1,终点的入度比出度多1;在欧拉回路中,所有点的出度等于入度。

通常找欧拉路径的算法就是dfs遍历,但是这里的判重是判重边,而非判重点。而且是当一个点所有的边都遍历结束后才会将该点算入答案。我们以一个点为例,若有m条自环路径,那么每条路径出就要开一层,一直往下递归。那么时间复杂度就变成m^2,这个时间复杂度很高,所有我们由此产生优化,遍历一条边那么就把它删掉,以防后面再次遍历到这条边,产生不必要的冗余。但是这里如果仅仅通过h[u]=ne[i]来删除,后面遍历时可以生效,但是回溯到前面遍历并不是再从开头开始,所以并没有生效(或者换个角度来理解,我们的删除相当于直接将头节点的指针往后移,但是中间节点的指针是没有改变的,所以实际上前面的循环应该已经到了中间那么节点的部分,它们之间的指向关系并没有改变,仍然会产生新的递归循环)时间复杂度还是很高,那么该如何处理呢,我们这里通过传入指针来实现动态的删除。

void dfs(int u)
{
    for (int &i = h[u]; ~i;)
    {
        if (used[i])
        {
            i = ne[i];
            continue;
        }

        used[i] = true;
        if (type == 1) used[i ^ 1] = true;

        int t;

        if (type == 1)
        {
            t = i / 2 + 1;
            if (i & 1) t = -t;
        }
        else t = i + 1;

        int j = e[i];
        i = ne[i];
        dfs(j);

        ans[ ++ cnt] = t;
    }
}

我们在定义的时候定义int &i=h[u],修改i的时候,h[u]也被同步修改了,往后递归的时候,我们每次都从h[u]开始,h[u]不是一成不变的,修改i相当于修改h[u],修改h[u]相当于修改中间节点的指向,中间节点的指向关系变了,那么往前回溯的过程中就不会出现刚刚m^2的问题了。

1123. 铲雪车(活动 - AcWing

这里有个很迷惑人的条件,铲雪的时候前进速度是20,不铲雪的时候前进速度是50,但是显然不铲雪的前提是学已经铲过了,那么就相当于这条路走了两边,当然是只走一遍更划算。每条道路的两个方向均需要铲雪,所以每条路径的出度等于入度,那么就是欧拉回路,所以我们可以保证每条路径只经过一次,所以我们统计出所有的路径再除20即可。这里的时间转化,我们可以先求出分钟数,然后再进行转化。

#include<bits/stdc++.h>
using namespace std;
int s,t;
double getd(double a,double b,double c,double d)
{
    double dx=a-c,dy=b-d;
    double res=dx*dx+dy*dy;
    return sqrt(res);
}
int main()
{
    scanf("%d%d",&s,&t);
    double a,b,c,d;
    double ans=0;
    while(~scanf("%lf%lf%lf%lf",&a,&b,&c,&d))
    {
        double sd=getd(a,b,c,d);
        ans += 2*sd;
    }
    int m=round(ans/1000/20*60);
    int h=m/60;
    m%=60;
    printf("%d:%02d",h,m);

}

1184. 欧拉回路(活动 - AcWing)

要注意,欧拉回路和欧拉路径中是可以有孤立点的,经过所有边,不一定经过所有点,所以dfs要找一个有出边的点开始遍历/

#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=400010;
int t,n,m;
int cnt;
int ans[M],used[M];
int h[N],e[M],ne[M],idx;
int din[N],dout[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
    for(int &i = h[u];~i;)
    {
        if(used[i])
        {
            i=ne[i];
            continue;
        }
        used[i]=1;
        if(t==1) used[i^1]=1;
        
        int res;
        if(t==1)
        {
            res=i/2+1;
            if(i&1) res*=-1;
        }
        else res=i+1;
        
        int j=e[i];
        i=ne[i];
        dfs(j);
        ans[++cnt]=res;
    }
    
}
int main()
{
    scanf("%d%d%d",&t,&n,&m);
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        if(t==1) add(b,a);
        din[b]++,dout[a]++;
    }
    if(t==1)
    {
        for(int i=1;i<=n;i++)
        {
            if(din[i]+dout[i]&1)
            {
                printf("NO");
                return 0;
            }
        }
    }
    else
    {
        for(int i=1;i<=n;i++)
        {
            if(din[i]!=dout[i])
            {
                printf("NO");
                return 0;
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(h[i]!=-1)
        {
            dfs(i);
            break;
        }
    }
    if(cnt!=m) printf("NO\n");
    else
    {
        printf("YES\n");
        for(int i=cnt;i>=1;i--)
        {
            printf("%d ",ans[i]);
        }
    }
}

 另外要注意我们dfs求得的路径是从终点到起点的,所以输出的时候需要倒着输出。

1124. 骑马修栅栏(1124. 骑马修栅栏 - AcWing题库

这里我们需要输出经过的节点,同时使得在有多组解的情况下输出节点序最小的那组解。那么我们首先应该找到最小的有出边的点,从这里开始遍历,另外为了使得后面的点也是小的在前面,我们需要先遍历所有出边中连接的点编号最小的出边,那么可以用邻接矩阵来存边。

本题不确定是求欧拉回路还是欧拉路径,所以我们先找出第一个有边的点,然后遍历查找是否有度为奇数点,如果有就替换,否则就直接搜欧拉回路。

#include<bits/stdc++.h>
using namespace std;
const int N=510;
int n;
int g[N][N],ans[N*N],cnt,d[N];
void dfs(int u)
{
    for(int i=1;i<=500;i++)
    {
        if(g[u][i])
        {
            g[u][i]--,g[i][u]--;
            dfs(i);
        }
    }
    ans[++cnt]=u;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        g[a][b]++,g[b][a]++;
        d[a]++,d[b]++;
    }
    int s=1;
    while(!d[s])s++;
    for(int i=s;i<=500;i++)
    {
        if(d[i]%2) 
        {
            s=i;
            break;
        }
    }
    dfs(s);
    for(int i=cnt;i>=1;i--) printf("%d\n",ans[i]);

}

1185. 单词游戏(活动 - AcWing

思路:这里如果将单词视为节点显然建边太麻烦了,我们可以用之前的一个思路,在一个单词的首尾字母之间建边,这样的话就只有26个点,实现复杂度一下就降低了。

 那么就要想是建有向边还是无向边,我们要注意顺序问题,所以建的是有向边。然后实际上不用真的去搜,只需要判断一下每个点的出度入度是否符合要求,然后判断一下边是否连通。这里判断边是否连通不用dfs,我们用并查集来判断。

#include<bits/stdc++.h>
using namespace std;
const int N=30,M=200010;
int st[M];
int dout[N],din[N];
int p[N];
int find(int x)
{
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        memset(st,0,sizeof st);
        memset(din,0,sizeof din);
        memset(dout,0,sizeof dout);
        for(int i=0;i<26;i++) p[i]=i;
        for(int i=1;i<=n;i++)
        {
            string s;
            cin>>s;
            int a=s[0]-'a',b=s[s.size()-1]-'a';
            st[a]=st[b]=1;
            dout[a]++,din[b]++;
            p[find(a)]=find(b);
        }
        int flag=1,sc=0,ec=0;
        for(int i=0;i<26;i++)
        {
            if(dout[i]-din[i]==1) sc++;
            else if(din[i]-dout[i]==1) ec++;
            else if(din[i]!=dout[i]) 
            {
                flag=0;
                break;
            }
        }
        if (flag && !(!sc && !ec || sc== 1 && ec == 1)) flag = 0;
       int rep=-1;
       for(int i=0;i<26;i++)
        {
            if(st[i])
            {
                if(rep==-1) rep=find(i);
                else if(rep!=find(i))
                {
                    flag=0;
                    break;
                }
            }
        }
    
        if(!flag) printf("The door cannot be opened.\n");
        else printf("Ordering is possible.\n");
    }
}

这题判断边是否连通也可以用dfs来写,只不过时间复杂度太高了,会超时,这里还是把代码放上。

#include<bits/stdc++.h>
using namespace std;
const int N=30,M=200010;
int st[M];
int dout[N],din[N];
int p[N];
int h[N],e[M],ne[M],idx;
int used[M];
int cnt;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
    for (int &i = h[u]; ~i;)
    {
        if (used[i])
        {
            i = ne[i];
            continue;
        }
        used[i] = true;
        int j = e[i];
        i = ne[i];
        dfs(j);
        cnt++;
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        memset(used,0,sizeof used);
        memset(din,0,sizeof din);
        memset(dout,0,sizeof dout);
        memset(h,-1,sizeof h);
        for(int i=1;i<=n;i++)
        {
            string s;
            cin>>s;
            int a=s[0]-'a',b=s[s.size()-1]-'a';
            dout[a]++,din[b]++;
            add(a,b);
        }
        int flag=1,sc=0,ec=0;
        for(int i=0;i<26;i++)
        {
            if(dout[i]-din[i]==1) sc++;
            else if(din[i]-dout[i]==1) ec++;
            else if(din[i]!=dout[i]) 
            {
                flag=0;
                break;
            }
        }
        if (flag && !(!sc && !ec || sc== 1 && ec == 1)) flag = 0;
        if(!flag) printf("The door cannot be opened.\n");
        //判连通
        else 
        {
            int s=0;
            while(!dout[s]) s++;
            for(int i=0;i<26;i++)
                if(dout[i]-din[i]==1)
                {
                    s=i;
                    break;
                }
            dfs(s);
            if(cnt<n) printf("The door cannot be opened.\n");
            else printf("Ordering is possible.\n");
        }
    }
}

所以可以见得,判断欧拉回路和欧拉路径,只要判断度和连通性的性质满足即可,方法不唯一。

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值