反正对于现在的我来说是好题。顺便膜po大犇和dingchao大犇。
网络流什么的还是再开一个专题好了。
欧拉回路问题参考论文《欧拉回路性质与应用探究》by 仇荣琦。
Description
小C为了减肥,他来到了瘦海,这是一个巨大的海,海中有n个小岛,小岛之间有m座桥连接,两个小岛之间不会有两座桥,并且从一个小岛可以到另外任意一个小岛。
现在小C想骑单车从小岛1出发,骑过每一座桥,到达每一个小岛,然后回到小岛1。小Q为了让小C减肥成功,召唤了大风,由于是海上,风变得十分大,经过每一座桥都有不可避免的风阻碍小C。小C十分ddt,于是用泡芙贿赂了你,希望你能帮他找出一条承受的最大风力最小的路线。
注意的是每条边能且只能经过一次,即欧拉回路。
Input
- Line 1:两个为空格隔开的整数 n ,
m 。- Line 2~m+1:每行由空格隔开的4个整数 a ,
b , c ,d 组成,表示第 i+1 行第 i 座桥连接小岛a 和 b ,从a 到 b 承受的风力为c ,从 b 到a 承受的风力为 d 。- 【数据范围】:
1≤m≤2000,2≤n≤1000 。- 1≤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 4Output
如果无法完成减肥计划,则输出NIE;
否则第一行输出最小的承受风力的最大值,且第二行输出一份方案(输出的是边的编号,第 i+1 行对应编号 i )。
Sample Output
4
4 3 2 1Output 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′ 的每个极大连通子图都有一条欧拉回路。(极大/小连通图:对于一个连通图来说,极大连通图即它本身,而极小连通图指这个连通图的生成树)- 设
C1
,
C2
是图
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的流从 u 向
v 流出,此时 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) 的边。
- 所有
degree>0
的点从
S
出发建边,所有
跑最大流算法,检查是否满流。(最大流初步)(待补充)
检查是否满流时,按照上述定义只需要判断所有与S相连的边上是否满流即可。
Code :(
请无视我因为取名癌把我男神名字丢结构体名称的举动)#include <bits/stdc++.h> #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; queue<int>Q;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; stack<int>stk; 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(num<w1)num=w1; if(num<w2)num=w2; } int ans=bisection(num);//二分枚举当前的最大风力 if(!~ans)puts("NIE"); else{ printf("%d\n",ans); Shirou.judge(ans); memset(head,-1,sizeof(head)); for(int i=1;i<=m;i++)//没有扔进网络流的单向边 if(Edges[i].w2>ans&&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; }
- 在图
G
中任意找到一个回路
- 设
C1
,
C2
是图
G
的两个没有公共边,但至少有一个公共顶点的简单回路,我们可以将其合并成一个新的简单回路