POJ 1637 Sightseeing tour (混合图欧拉回路)

建图方法:

把该图的无向边随便定向,计算每个点的入度和出度。如果有某个点出入度之差为奇数,那么肯定不存在欧拉回路。因为欧拉回路要求每点入度 = 出度,也就是总度数为偶数,

若在奇数度点必不能有欧拉回路。

好了,现在每个点入度和出度之差均为偶数。那么将这个偶数除以2,得x。也就是说,对于每一个点,只要将x条边改变方向(入>出就是变入,出>入就是变出),就能保证出=

入。如果每个点都是出=入,那么很明显,该图就存在欧拉回路。

现在的问题就变成了:我该改变哪些边,可以让每个点出=入?构造网络流模型。首先,有向边是不能改变方向的,要之无用,删。一开始不是把无向边定向了吗?定的是什么

向,就把网络构建成什么样,边长容量上限1。另新建s和t。对于入>出的点u,连接边(u, t)、容量为x,对于出>入的点v,连接边(s, v),容量为x(注意对不同的点x不同)。之后,

察看是否有满流的分配。有就是能有欧拉回路,没有就是没有。欧拉回路是哪个?察看流值分配,将所有流量非 0(上限是1,流值不是0就是1)的边反向,就能得到每点入度=出

度的欧拉图。

由于是满流,所以每个入>出的点,都有x条边进来,将这些进来的边反向,OK,入=出了。对于出>入的点亦然。那么,没和s、t连接的点怎么办?和s连接的条件是出>入,和t相

接的条件是入>出,那么这个既没和s也没和t连接的点,自然早在开始就已经满足入=出了。那么在网络流过程中,这些点属于“中间点”。我们知道中间点流量不允许有累积的,这样,进去多少就出来多少,反向之后,自然仍保持平衡。

欧拉图的判定:

1.基图连通,====》并查集判定

2.无向图:无奇度顶点

    有向图:所有顶点出入度相等 ====》统计判定

#include <iostream>
#include<stdio.h>
#include<string.h>
#include<queue>
#include<cmath>
using namespace std;
const int maxn=200+10;
const int maxm=2000+10;
const int inf=1e9+7;
int m,n,s;
struct Edge
{
    int to,next;
    int cap;
}edge[maxm*2];
int head[maxn],size;
int dis[maxn];//层次网络的编号值,离汇点的距离
int father[maxn];
int outd[maxn],ind[maxn];
void clear()//链式前向星
{
    size=0;
    memset(head,-1,sizeof(head));
    memset(ind,0,sizeof(ind));
    memset(outd,0,sizeof(outd));
}
void _add(int from,int to,int cap)
{
    edge[size].to=to;
    edge[size].cap=cap;
    edge[size].next=head[from];
    head[from]=size++;
}
void add(int a,int b,int c)
{
    _add(a,b,c);
    _add(b,a,0);
}
bool bfs(int s,int t)//对层次网络进行编号
{
    queue<int> Q;
    memset(dis,-1,sizeof(dis));//将所有点的层次设为-1
    dis[s]=0;//s入队
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();//取队首元素
        if(v==t)return true;
        for(int i=head[v];i!=-1;i=edge[i].next)//找邻接点
        {
            if(dis[edge[i].to]==-1&&edge[i].cap>0)//如果存在邻接点并且还未被编入层次网络
            {
                dis[edge[i].to]=dis[v]+1;
                Q.push(edge[i].to);
            }
        }
    }
    return false;
}
int dfs(int x,int t,int maxc)//从节点x到汇点t的最大可增广流量,maxc表示当前可容许的最大容量
{
    if(x==t)return maxc;
    int ret,flow;ret=0;
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        if(edge[i].cap>0&&dis[edge[i].to]==dis[x]+1)
        {
            flow=dfs(edge[i].to,t,min(maxc-ret,edge[i].cap));
            edge[i].cap-=flow;
            edge[i^1].cap+=flow;
            ret+=flow;
            if(ret==maxc)return ret;
        }
    }
    return ret;
}
int dinic(int s,int t)
{
    int ans=0;
    while(bfs(s,t))//每次重建层次网络
    ans+=dfs(s,t,inf);//如果找到一条增广路
    return ans;
}
void make_set()
{
    for(int i=1;i<=m;i++)
    father[i]=i;
}
int find_set(int x)
{
    if(x!=father[x])
    father[x]=find_set(father[x]);
    return father[x];
}
void union_set(int x,int y)
{
    int a=find_set(x);
    int b=find_set(y);
    if(a==b)return;
    father[a]=b;
}
bool is_connect()
{
    int cnt=-1;
    for(int i=1;i<=m;i++)
    if(father[i]==i)cnt++;
    if(cnt==0)return true;
    return false;
}
int main()
{
    freopen("in","r",stdin);
    int a,b,c;
    scanf("%d",&n);
    while(n--)
    {
        clear();
        scanf("%d %d",&m,&s);
        make_set();
        for(int i=0;i<s;i++)
        {
            scanf("%d %d %d",&a,&b,&c);
            union_set(a,b);
            outd[a]++;ind[b]++;
            if(c==0)//如果是无向的,变为单向的。
            add(a,b,1);
        }
        if(!is_connect())puts("impossible");
        else
        {
            int tot=0;
            bool flag=false;
            for(int i=1;i<=m;i++)
            {
                if(outd[i]>ind[i])
                {
                    int ans=outd[i]-ind[i];
                    if(ans&1){flag=true;break;}
                    add(0,i,ans/2);
                    tot+=ans/2;
                }
                else
                {
                    int ans=ind[i]-outd[i];
                    if(ans&1){flag=true;break;}
                    add(i,m+1,ans/2);
                }
            }
            if(flag)puts("impossible");
            else
            {
                if(dinic(0,m+1)==tot)puts("possible");//检查是否满流
                else puts("impossible");
            }
        }
    }
    return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值