【JZOJ5840】【省选模拟2018.8.22】Miner(思维+欧拉回路)

本文介绍了一种解决欧拉路径问题的算法,特别是在无向图中寻找最优路径的方法。考虑到图可能不连通,文章详细解释了如何确定最少传送次数,并提供了一种可行的解决方案。此外,还给出了具体的时间复杂度分析。

Problem

  给定一张n(105)n(≤105)个点、m(105)m(≤105)条边的无向图(可能存在自环和重边,图可能不连通)。小C可任选起点开始,进行以下操作的一个:

  • 0 v,选择一条相邻的还未经过的边走到v
  • 1 v,传送到点v

    小C要走一个欧拉路径。求最小传送次数,并给出一种方案。

Solution

  • 先考虑如何求答案(最少添加多少条边)。
  • 注意到图可能不连通,那么我们肯定是跑完一个联通块内的,再去其他的联通块。因此,每个联通块的答案应是相对独立的。
  • 设有k个联通块,第i个中有ci个奇点,则答案为ki=1max(1,c2)1∑i=1kmax(1,c2)−1
  • 因为根据无向图中欧拉路径的判定条件:奇点数为0或2,并且这两个奇点其中一个为起点另外一个为终点。我们每次传送到一个奇点,然后通过一条欧拉路径走到另一个奇点;然后再传送到一个奇点,再走到另一个奇点……如此往复。后面-1是因为选起点不计入传送次数。

  • 知道了原因后,构造方案也非常简单了。
  • 对于每一个联通块,我们求一波它里面所有的奇点,如果没有奇点,则表明一定存在欧拉回路,直接跑圈套圈算法。
  • 如果有奇点呢?
  • 囿于一个图中所有点的度数和肯定是偶数,所以定然是存在偶数个奇点。我们新建一个超级源S,从S连向所有奇点,这样所有奇点度数+1变成偶点,S也是偶点,于是图中便不存在奇点了,那么再跑圈套圈算法。

  • 至此,我们知道了跑这个联通块的顺序。那我讲一下方案构造。
  • 如果是从S跑到点x,那x肯定是奇点,于是操作是1 x。
  • 如果从某个点跑到S,那肯定是从奇点跑到S,我们就不用管(因为S是虚点)。
  • 如果从某个点x跑到点y,那么操作是0 y。

  • 时间复杂度:O(n+m)O(n+m)

Code

#include <bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define fo(i,a,b) for(i=a;i<=b;i++)
#define re(i,x) for(int i=x; i; i=ne[i])
using namespace std;
typedef pair<int,int> P;

const int N=11e4,M=N<<3;
int i,j,n,m,x,y,tot=1,to[M],ne[M],fin[N],deg[N],d[M],top,sta[M],ans,q;
bool vis[N],bz[N],went[M];
P o[M];

void link(int x,int y)
{
    to[++tot]=y; ne[tot]=fin[x]; fin[x]=tot; deg[x]++;
    to[++tot]=x; ne[tot]=fin[y]; fin[y]=tot; deg[y]++;
}

void fill(int x)
{
    vis[x]=1; 
    if(deg[x]&1) sta[++top]=x;
    re(i,fin[x]) if(!vis[to[i]])fill(to[i]);
}

void Euler(int x)
{
    d[d[0]=1]=x;
    while(d[0])
    {
        rep:x=d[d[0]];
        if(!bz[x]) bz[x]=1;
        for(j=fin[x]; j; j=fin[x])
        {
            fin[x]=ne[j];
            if(!went[j]) 
            {
                went[j]=went[j^1]=1; d[++d[0]]=to[j];
                goto rep;
            }
        }
        sta[++top]=x;
        d[0]--;
    }
}

int main()
{
    freopen("miner.in","r",stdin);
    freopen("miner.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m)
    {
        scanf("%d%d",&x,&y);
        link(x,y); 
    }   

    fo(i,1,n)
        if(deg[i]&&!vis[i])
        {
            top=0;
            fill(i);
            fin[0]=0;   
            if(!top)
                    link(0,i), link(i,0);
            else    while(top)link(0,sta[top--]);
            Euler(bz[0]=0); 
            while(top>1)
            {
                x=sta[top--];
                if(x) 
                        o[++q]=mp(0,x);
                else    o[++q]=mp(1,sta[top--]), ans++;
            }
        }   

    printf("%d\n%d\n",ans-1,o[1].se);
    fo(i,2,q) printf("%d %d\n",o[i].fi,o[i].se);
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值