HDU 1956 Sightseeing tour(混合图欧拉回路)

**

HDU 1956 Sightseeing tour(混合图欧拉回路)

**
题目链接

The city executive board in Lund wants to construct a sightseeing tour by bus in Lund, so that tourists can see every corner of the beautiful city. They want to construct the tour so that every street in the city is visited exactly once. The bus should also start and end at the same junction. As in any city, the streets are either one-way or two-way, traffic rules that must be obeyed by the tour bus. Help the executive board and determine if it’s possible to construct a sightseeing tour under these constraints.

Input

On the first line of the input is a single positive integer n, telling the number of test scenarios to follow. Each scenario begins with a line containing two positive integers m and s, 1 <= m <= 200,1 <= s <= 1000 being the number of junctions and streets, respectively. The following s lines contain the streets. Each street is described with three integers, xi, yi, and di, 1 <= xi,yi <= m, 0 <= di <= 1, where xi and yi are the junctions connected by a street. If di=1, then the street is a one-way street (going from xi to yi), otherwise it’s a two-way street. You may assume that there exists a junction from where all other junctions can be reached.

Output

For each scenario, output one line containing the text “possible” or “impossible”, whether or not it’s possible to construct a sightseeing tour.

Sample Input

4
5 8
2 1 0
1 3 0
4 1 1
1 5 0
5 4 1
3 4 0
4 2 1
2 2 0
4 4
1 2 1
2 3 0
3 4 0
1 4 1
3 3
1 2 0
2 3 0
3 2 0
3 4
1 2 0
2 3 1
1 2 0
3 2 0

Sample Output

possible
impossible
impossible
possible

题意:判断一个混合图中是否存在欧拉回路

**

欧拉回路(此题不需要)

**

欧拉通路:图连通,并且通过图中每条边且只通过一次。
欧拉回路:图连通,并且通过图中每条边且只通过一次,并且回到原起点。
无向图是否具有欧拉通路或回路的判定:
欧拉通路:图连通,并且图中只有0个或2个度为奇数的节点
欧拉回路:图连通,并且图中所有节点度均为偶数
有向图是否具有欧拉通路或回路的判定:
欧拉通路:图连通,并且除2个端点外其余节点入度=出度;1个端点入度比出度1;一个端点入度比出度小1 或 所有节点入度等于出度
欧拉回路:图连通,并且所有节点入度等于出度
求欧拉回路的方法:
1.dfs
dfs搜索,不能再往下走便回溯,回溯时记录路径,回溯时不清除对边的标记,最后求出来的路径就是欧拉回路。

void dfs(int v)
{
    for(int i=0; i<graph[v].size(); i++)
    {
        int e=graph[v][i];
        if(!visited[e])
        {
            cnt++;
            if(graph[e].size()%2) in++;
            visited[e]=true;
            dfs(e);
            printf
        }
    }
}
int main()
{
    int t;
    cin>>t;
    while (t--)
    {
        cin>>n>>m;
        for(int i=0; i<=n; i++) graph[i].clear();
        for(int i=0; i<m; i++)
        {
            int x,y;
            cin>>x>>y;
            graph[x].push_back(y);
            graph[y].push_back(x);
        }
        cnt=0;
        in=0;
        memset(visited,false,sizeof(visited));
        d
        fs(1);
        if((m==0&&n==1)||(cnt==n&&(in==0||in==2))) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}

2.并查集

int find(int x)
{
    if(x==fa[x]) return x;
    else
    {
        return fa[x]=find(fa[x]);
    }
}
void unite(int x,int y)
{
    int rx=find(x);
    int ry=find(y);
    fa[ry]=rx;
}
for(int i=1; i<=m; i++)
{
    int x,y;
    scanf("%d%d",&x,&y);
    degree[x]++;
    degree[y]++;
    unite(x,y);
}
int root=find(1),cnt=0,d=0;
for(int i=1; i<=n; i++)
{
    if(find(i)!=root) cnt++;
    if(degree[i]%2) d++;
}
if(!cnt&&(d==0||d==2)) printf("Yes\n");
else printf("No\n");

3.Fleury(佛罗莱算法)
设G 为一无向欧拉图,求G 中一条欧拉回路的算法为:

  1. 任取G 中一顶点v0,令P0 = v0;
  2. 假设沿Pi =v0e1v1e2v2 …eivi 走到顶点vi,按下面方法从E(G)- { e1, e2, …, ei }中选ei+1:
    a) ei+1 与vi 相关联;
    b) 除非无别的边可供选择,否则ei+1
    不应该是Gi = G - { e1, e2, …, ei }中的桥。
  3. 当2)不能再进行时算法停止。
    可以证明的是,当算法停止时,所得到的简单回路Pm = v0e1v1e2v2…emvm, (vm = v0)为G 中一条
struct stack
{
    int top,node[M];
} s;
int e[M][M],n;
void dfs(int x)
{
    int i;
    s.node[++s.top]=x;    //cout<<s.top<<' '<<s.node[s.top]+1<<endl;
    for(i=0; i<n; i++)
    {
        if(e[i][x]>0)
        {
            e[i][x]=e[x][i]=0;  //删除这条边
            dfs(i);
            break;
        }
    }
}
void fleury(int x)
{
    int i,flag;
    s.top=0;
    s.node[s.top]=x;
    while(s.top>=0)
    {
        flag=0;
        for(i=0; i<n; i++)
        {
            if(e[s.node[s.top]][i]>0)
            {
                flag=1;
                break;
            }
        }
        if(!flag) printf("%d ",s.node[s.top--]+1)
            else dfs(s.node[s.top--]);
    }
    puts("");
}
int main( )
{
    int i,j,u,v,m,degree,num=0,start=0;
    scanf("%d%d",&n,&m);
    mem(e,0);
    for(i=0; i<m; i++)
    {
        scanf("%d%d",&u,&v);
        e[u-1][v-1]=e[v-1][u-1]=1;
    }
    for(i=0; i<n; i++)
    {
        degree=0;
        for(j=0; j<n; j++)
            degree+=e[i][j];
        if(degree&1)
        {
            start=i;
            num++;
        }
    }
    if(num==0||num==2) fleury(start);
    else printf("No Euler path\n");
    return 0;
}
//先找到第一个有奇数个度的点作为起点,推入栈,while(栈非空)找与栈顶元素相连的元素推入栈,边删除,直到栈顶元素

**

邻接表

**

//运用数组
void add(int u,int v,int w)
{
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    edge[cnt].to=v;
    head[u]=cnt++;
}
for(int i= head[x]; i!=-1; i=edge[i].next) //循环一个点(比如u)的所有边(u-v,u-v1...)。递归是递归v的所有边。
{
    if(!s[edge[i].to])
    {
        dfs(edge[i].to);
    }
}

例如

1 2
2 3
3 4
1 3
4 1
1 5
4 5

edge[0].to=2,edge[0].next=-1,head[1]=0;
edge[1].to=3,edge[1].next=-1,head[2]=1;
edge[2].to=4,edge[2].next=-1,head[3]=2;
edge[3].to=3,edge[3].next= 0,head[1]=3;
edge[4].to=1,edge[4].next=-1,head[4]=4;
edge[5].to=5,edge[5].next= 3,head[1]=5;
edge[6].to=5,edge[6].next= 4,head[4]=6;

每条边的next指向上一条与这条边同起点的边
**

混合图的欧拉回路

**

1、随意定向
在混合图中,对于双向边的处理除了拆边之外,还有任意定向。先对全图的双向边进行任意定向,接着使用上文的欧拉回路算法,很显然,无法得到结果。但是通过这一步,至少可以确定这样一件事实,如果一个点的出度加入度一定是奇数的话,那么这个图一定没有欧拉回路。
而随意定向是没有依据的,但是可以使用这样的随机化处理方法,再使用恰当的调整方法构造出解。
2、自调整方法
所谓的自调整方法就是将其中的一些边的方向调整回来,使所有的点的出度等于入度。但是有一条边的方向改变后,可能会改变一个点的出度的同时改变另一个点的入度,相当于一条边制约着两个点。同时有些点的出度大于入度,迫切希望它的某些点出边转向;而有些点的入度大于出度,迫切希望它的某些入边转向。这两条边虽然需求不同,但是他们之间往往一条边转向就能同时满足二者。
具体步骤:
1、另x = |入度-出度|/2;对于不同的点有不同的x值,这个x值代表它们在邻接表中相应调整x条就能让出度等于入度。
2、以把图中的点转换为一个二分图,每个点的x值就是它们的点权。
3、置源点S向所有出度>入度的点连边;设置汇点T,所有入度大于出度的点连边,将各自的点权转换为边权。
4、最后将原图中所有暂时定向的无向边加上一个1的容量,方向不变,而有向边不能改变方向,不需连边。

可以发现,从源点S出发的一个单位流将会一个“无向边”的容量变为0,使得两端的点权各自减1,其实这就是在模拟一次对无向边方向的调整。当把图建好后,依靠最大流性质可以最大可能地无冲突调整边的方向,并最终使得每个点的点容量都达到满流。
最后,还要对那些图中出度等于入度的点做适当分析,它们作为一个“中间点”,由于流平衡性质,不会留下任何流量值,对于那些真正需要调整的点不会带来任何影响。
最后,如何得到答案?那就是检查从源点出发的每条边是否都满流,如果有一条边没有满流,说明有一个点没有调整到入度等于出度,于是整个图不存在欧拉回路。
最大流(Dinic算法)

在一个有向图上选择一个源点,一个汇点,每一条边上都有一个流量上限(以下称为容量),即经过这条边的流量不能超过这个上界,同时,除源点和汇点外,所有点的入流和出流都相等,而源点只有流出的流,汇点只有汇入的流。这样的图叫做网络流

源点:只有流出去的点
汇点:只有流进来的点
流量:一条边上流过的流量
容量:一条边上可供流过的最大流量
残量:一条边上的容量-流量

网络流的所有算法都是基于一种增广路的思想,下面首先简要的说一下增广路思想,其基本步骤如下:
1.找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是小于而不是小于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
2.找到这条路径上最小的F[u][v](我们设F[u][v]表示u->v这条边上的残量即剩余流量),下面记为flow
3.将这条路径上的每一条有向边u->v的残量减去flow,同时对于起反向边v->u的残量加上flow
4.重复上述过程,直到找不出增广路,此时我们就找到了最大流

Dinic算法引入了一个叫做分层图的概念。具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度,然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。

int s,t;//源点和汇点
int cnt;//边的数量,从0开始编号。
int Head[maxN];//每一个点最后一条边的编号
int Next[maxM];//指向对应点的前一条边
int V[maxM];//每一条边指向的点
int W[maxM];//每一条边的残量
int Depth[maxN];//分层图中标记深度

Dinic主过程:

int Dinic()
{
    int Ans=0;//记录最大流量
    while (bfs())
    {
        while (int d=dfs(s,inf))
            Ans+=d;
    }
    return Ans;
}

bfs分层图过程

bool bfs()
{
    queue<int> Q;//定义一个bfs寻找分层图时的队列
    while (!Q.empty())
        Q.pop();
    memset(Depth,0,sizeof(Depth));
    Depth[s]=1;//源点深度为1
    Q.push(s);
    do
    {
        int u=Q.front();
        Q.pop();
        for (int i=Head[u];i!=-1;i=Next[i])
            if ((W[i]>0)&&(Depth[V[i]]==0))//若该残量不为0,且V[i]还未分配深度,则给其分配深度并放入队列
            {
                Depth[V[i]]=Depth[u]+1;
                Q.push(V[i]);
            }
    }
    while (!Q.empty());
    if (Depth[t]==0)//当汇点的深度不存在时,说明不存在分层图,同时也说明不存在增广路
        return 0;
    return 1;
}

dfs寻找增广路过程

int dfs(int u,int dist)//u是当前节点,dist是当前流量
{
    if (u==t)//当已经到达汇点,直接返回
        return dist;
    for (int i=Head[u];i!=-1;i=Next[i])
    {
        if ((Depth[V[i]]==Depth[u]+1)&&(W[i]!=0))//注意这里要满足分层图和残量不为0两个条件
        {
            int di=dfs(V[i],min(dist,W[i]));//向下增广
            if (di>0)//若增广成功
            {
                W[i]-=di;//正向边减
                W[i^1]+=di;反向边加
                return di;//向上传递
            }
        }
    }
    return 0;//否则说明没有增广路,返回0
}

Dinic算法的优化
Dinic算法还有优化,这个优化被称为当前弧优化,即每一次dfs增广时不从第一条边开始,而是用一个数组cur记录点u之前循环到了哪一条边,以此来加速
摘自:非常好的讲解
本题代码

#include<bits/stdc++.h>
#define N 2010
#define M 50010
#define inf 0x3f3f3f3f
using namespace std;
int cnt,head[N],to[M],next[M],deep[N],amount[M],in[N],out[N],cur[N],n,m;
void  Add(int u,int v,int w)
{
    next[cnt]=head[u];
    to[cnt]=v;
    amount[cnt]=w;
    head[u]=cnt;
    //printf("%d %d\n",next[cnt],to[cnt]);
    cnt++;
}
void Add_Edge(int u,int v,int w)
{
    Add(u,v,w);
    Add(v,u,0);
}
int bfs(int s,int t)
{
    queue<int>q;
    memset(deep,0,sizeof(deep));
    deep[s]=1;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u]; i!=-1; i=next[i])
        {
            if(amount[i]>0&&deep[to[i]]==0)
            {
                deep[to[i]]=deep[u]+1;
                q.push(to[i]);
            }
        }
    }
    if(deep[t]==0)
        return 0;
    return 1;
}
int dfs(int s,int t,int f)
{
    if(s==t)
        return f;
    for(int i=cur[s]; i!=-1; i=next[i])
    {
        if((deep[to[i]]==deep[s]+1)&&amount[i]>0)
        {
            int d=dfs(to[i],t,min(f,amount[i]));
            if(d>0)
            {
                amount[i]-=d;
                amount[i^1]+=d;
                return d;
            }
        }
    }
    return 0;
}
int dinic(int s,int t)
{
    int ans=0;
    while(bfs(s,t))
    {
        for (int i=0; i<=n; i++) cur[i] = head[i];
        int d;
        while((d=dfs(s,t,inf))>0)
        {
            ans+=d;
        }
    }
    return ans;
}
int main()
{
    std::ios::sync_with_stdio(false);
    int T;
    cin>>T;
    while(T--)
    {
        int a,b,c;
        cin>>n>>m;
        memset(head, -1, sizeof(head));
        memset(in, 0, sizeof(in));
        memset(out, 0, sizeof(out));
        cnt = 0;
        for(int i=1; i<=m; i++)
        {
            cin>>a>>b>>c;
            in[b]++;
            out[a]++;
            if(c==0)
                Add_Edge(a,b,1);
        }
        int flag=1,num,sum=0;
        int s=0,t=n+1;
        for(int i=1; i<=n&&flag; i++)
        {
            num=abs(in[i]-out[i]);
            if(num%2)
                flag=0;
            num=in[i]-out[i];
            if(num>0)
                Add_Edge(i,t,num/2);
            else if(num<0)
            {
                Add_Edge(s,i,-num/2);
                sum-=num/2;
            }
        }
        if (!flag) cout << "impossible" << endl;
        else
        {
            num = dinic(s, t);
            if (num == sum)
            {
                cout << "possible" << endl;
            }
            else cout << "impossible" << endl;
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值