频道栏目
首页 > 程序开发 > 软件开发 > 其他 > 正文
图论欧拉回路初步 & BZOJ2095 POI2010 Bridges
2016-10-14 09:14:00         来源:Kanosword的博客  
收藏   我要投稿

Description

小C为了减肥,他来到了瘦海,这是一个巨大的海,海中有n个小岛,小岛之间有m座桥连接,两个小岛之间不会有两座桥,并且从一个小岛可以到另外任意一个小岛。

现在小C想骑单车从小岛1出发,骑过每一座桥,到达每一个小岛,然后回到小岛1。小Q为了让小C减肥成功,召唤了大风,由于是海上,风变得十分大,经过每一座桥都有不可避免的风阻碍小C。小C十分ddt,于是用泡芙贿赂了你,希望你能帮他找出一条承受的最大风力最小的路线。

注意的是每条边能且只能经过一次,即欧拉回路

Input

Line 1:两个为空格隔开的整数nm。 Line 2~m+1:每行由空格隔开的4个整数abcd组成,表示第i+1行第i座桥连接小岛ab,从ab承受的风力为c,从ba承受的风力为d。 【数据范围】:
1≤m≤2000,2≤n≤10001≤a≠b≤n,1≤c,d≤1000

Sample Input

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

Output

如果无法完成减肥计划,则输出NIE;

否则第一行输出最小的承受风力的最大值,且第二行输出一份方案(输出的是边的编号,第i+1行对应编号i)。

Sample Output

4
4 3 2 1

Output Details

样例如图所示:

这里写图片描述


首先这一题套在外面的二分答案是非常明显的,怎么说题目中也频繁出现了“最大值最小”之类的字眼。二分枚举这个最大风力,那么我们可以把剩下的图提取出来。

根据题意要求:每条边必须经过一次,而点可以多次经过,并且最后要回到原点。这样的定义指向欧拉回路。于是本题的任务变成:

对于重构的混合图(既包含有向边又包含无向边)进行欧拉回路判定,再输出对于最小符合题意风力重构的图的欧拉回路方案。

图论欧拉回路初步:

引入:(我这引入也真TM有个性

NOIP初赛有这样一道题目:

[NOIP2007 提高组初赛 T9]欧拉图G是指可以构成一个闭回路的图,且图G的每一条边恰好在这个闭回路上出现一次(即一笔画成)。在以下各个描述中,不一定是欧拉图的是(D)。

A. 图G中没有度为奇数的顶点 。 B. 包含欧拉环游的图。 C. 包含欧拉闭迹的图。 D. 存在一条回路,通过每个顶点恰好一次。 E. 本身为闭迹的图。

欧拉回路的基本概念顺便解决上述问题):设图G=(V,E)

欧拉路径欧拉迹):图G中经过每条边一次并且仅一次的路径成为欧拉路径。 欧拉回路欧拉闭迹):图G中经过每条边一次并且仅一次的回路成为欧拉路径。 欧拉图:存在欧拉回路的图称为欧拉图。 半欧拉图:存在欧拉路径但不存在欧拉回路的图称为半欧拉图。

注意D选项是错误的。(哈密顿图:通过图G的每个结点有且只有一次的回路,就是哈密顿回路,存在哈密顿回路的图就是哈密顿图)

欧拉图的判定定理

无向图G为欧拉图,当且仅当G为连通图,且所有顶点的度为偶数。 有向图G为欧拉图,当且仅当G基图连通,且所有顶点的入度等于出度。

欧拉图的性质

C是欧拉图G中的一个简单回路,将C中的边从图G中删去得到一个新的图G′,则G′的每个极大连通子图都有一条欧拉回路。(极大/小连通图:对于一个连通图来说,极大连通图即它本身,而极小连通图指这个连通图的生成树) 设C1C2是图G的两个没有公共边,但至少有一个公共顶点的简单回路,我们可以将其合并成一个新的简单回路C′

1)求欧拉回路

根据上述性质,得到求欧拉回路的算法(注意是在欧拉图上):

在图G中任意找到一个回路C。 将图G中属于回路C的边删除。 在残留图的各极大子图中分别寻找欧拉回路。 将各极大连通子图的欧拉回路合并到C中得到图G的欧拉回路。
void Euler u //伪代码
    while(next v)
        if e(u,v) unmarked 
            mark e(u,v),e(v,u)
            Euler v
            stack.push e(u,v)

上述伪代码的时间复杂度O(E),由于边数过多会有爆栈危险,我们也会采用非递归形式。(待添加)

以上内容引自《欧拉回路性质与应用探究》by 仇荣琦。

2)混合路欧拉路径判定

对每条无向边进行随机定向

通过随机定边,我们暂时构造出了一个有向图,根据上述欧拉回路的有向图判定定理,我们在接下来的处理中只要通过调整该有向图中由无向边变成的有向边的方向,使得所有节点的入度=出度即可。

对当前的新图构建网络。

我们按照当前点的出入度进行建边:

假设该点的degree=u+?u?degree>0表示需要更改从它连出去的degree条边,同理degree<0表示需要更改从它连出去的?degree条边。

再定义e(u,v,1)表示有值为1的流从uv流出,此时edge(u,v)被反向。同理,我们对于一个degree>0的点,为了让其degree减小,我们假设一个虚拟源点S,此时若e(S,u,degree2)中的流量能全部跑光,则说明u后面的边可以通过修改方向,使得u的出度=入度。

那么我们对按照以下方式建边:

所有degree>0的点从S出发建边,所有degree<0的点向T建边。 如果对于如果确定一条无向边的方向为edge(u,v),则建一条(v,u,1)的边。

跑最大流算法,检查是否满流。(最大流初步)(待补充)

检查是否满流时,按照上述定义只需要判断所有与S相连的边上是否满流即可。


Code:(请无视我因为取名癌把我男神名字丢结构体名称的举动

#include 
#define M 2005
#define S 0
#define T 2004
#define inf 0x3f3f3f3f
using namespace std;
int n,m;
struct edge{int u,v,w1,w2,id;}Edges[M];

struct Union_Find_Set{
    int fa[M],cnt;
    void Init(){
        for(int i=1;i<=n;i++)fa[i]=i;
        cnt=n;
    }
    int Getfa(int x){
        return (fa[x]==x)?fa[x]:fa[x]=Getfa(fa[x]);
    }
    void Union(int u,int v){
        u=Getfa(u),v=Getfa(v);
        if(u!=v)fa[u]=v,cnt--;
    }
}Emiya;//判断重构图是否为连通图

struct Max_Flow{
    struct node{int v,f,nxt,id;}Nodes[M*M];
    int head[M],tot,degree[M],level[M];
    void Init(){
        memset(head,-1,sizeof(head));
        memset(degree,0,sizeof(degree));
        tot=1;
    }
    void Add_edge(int u,int v,int w,int id){
        Nodes[++tot]=(node){v,w,head[u],id};head[u]=tot;
        Nodes[++tot]=(node){u,0,head[v],id};head[v]=tot;
    }//邻接表
    bool bfs(){
        memset(level,-1,sizeof(level));
        level[S]=1;
        queueQ;Q.push(S);
        while(!Q.empty()){
            int u=Q.front();Q.pop();
            for(int j=head[u];~j;j=Nodes[j].nxt)
                if(Nodes[j].f&&!~level[Nodes[j].v]){
                    level[Nodes[j].v]=level[u]+1;
                    Q.push(Nodes[j].v);
                    if(Nodes[j].v==T)return true;
                }
        }
        return false;
    }
    int Dinic(int u,int flow){
        if(u==T)return flow;
        int left=flow;
        for(int j=head[u];~j&&left;j=Nodes[j].nxt)
            if(Nodes[j].f&&level[u]+1==level[Nodes[j].v]){
                int tmp=Dinic(Nodes[j].v,min(left,Nodes[j].f));
                if(tmp){
                    left-=tmp;
                    Nodes[j].f-=tmp;
                    Nodes[j^1].f+=tmp;
                }
            }
        if(left)level[u]=-1;
        return flow-left;
    }//最大流Dinic算法,Ford_Fulkerson或Edmonds_Karp算法亦可以套用

    bool judge(int val){
        Emiya.Init();Init();
        for(int i=1;i<=m;i++){
            int u=Edges[i].u,v=Edges[i].v;
            if(Edges[i].w2<=val){//degree_in +1 degree_out -1 
                Emiya.Union(u,v);
                Add_edge(u,v,1,Edges[i].id);
                degree[u]++;
                degree[v]--;
            }else if(Edges[i].w1<=val){
                Emiya.Union(u,v);
                degree[u]--;
                degree[v]++;
            }
        }
        if(Emiya.cnt>1)return false;//原图不连通 
        for(int i=1;i<=n;i++){
            if(degree[i]&1)return false;//不满足欧拉回路性质
            if(degree[i]>0)Add_edge(S,i,degree[i]>>1,S);
            else if(degree[i]<0)Add_edge(i,T,-degree[i]>>1,T);
        }

        while(bfs())Dinic(S,inf);//使劲跑网络流,如果构成满流则说明找到了欧拉回路 
        for(int j=head[S];~j;j=Nodes[j].nxt)if(Nodes[j].f)return false;
        return true;
    }
}Shirou;

int bisection(int R){
    int L=1,res=-1;
    while(L<=R){
        int mid=L+R>>1;
        if(Shirou.judge(mid)){//跑欧拉回路 
            R=mid-1;
            res=mid;
        }else L=mid+1;
    }return res;
}

int head[M],tot=0;
struct New_Graph{int v,nxt,id;bool vis;}Graph[M<<1];
void Add_New_Edge(int u,int v,int id){
    Graph[++tot]=(New_Graph){v,head[u],id,0};head[u]=tot;
}
bool f=false;
stackstk;
void Euler_Print(int u){
    for(int j=head[u];~j;j=Graph[j].nxt){
        if(!Graph[j].vis){
            Graph[j].vis=true;
            Euler_Print(Graph[j].v);
            stk.push(Graph[j].id);
        }
    }
}//打印解部分

int main(){
    int num=0;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v,w1,w2;
        scanf("%d %d %d %d",&u,&v,&w1,&w2);
        if(w1>w2)swap(u,v),swap(w1,w2); 
        Edges[i]=(edge){u,v,w1,w2,i};
        if(numans&&Edges[i].w1<=ans)
                Add_New_Edge(Edges[i].u,Edges[i].v,Edges[i].id);
        for(int u=1;u<=n;u++)//网络流跑出来的满流边
            for(int j=Shirou.head[u];~j;j=Shirou.Nodes[j].nxt){
                Max_Flow::node now=Shirou.Nodes[j];
                if(!now.f&&now.v!=S&&now.v!=T)Add_New_Edge(u,now.v,now.id);
            }
        Euler_Print(1);
        while(!stk.empty()){
            printf("%d%c",stk.top(),stk.size()==1?'\n':' ');
            stk.pop();
        }
        puts("");
    }
    return 0;
}
点击复制链接 与好友分享!回本站首页
上一篇:PHP基础教程十三之反射、对象序列化
下一篇:Java基础学习总结(66)——配置管理库typesafe.config教程
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站