題意:給出一個混合圖(有的邊有向,有的邊無向),問此圖是否存在歐拉回路。
先說說歐拉回路吧,起點和終點相同,經過圖G的每條邊一次,且只經過一次的路徑稱為歐拉回路。
按照圖的不同分為:無向圖歐拉回路、有向圖歐拉回路和混合圖歐拉回路。
判斷一個圖是否存在歐拉回路:
1.無向圖:圖連通,且圖中均為偶度頂點。
2.有向圖:圖連通,且圖中所有頂點出入度相等。
3.混合圖:混合圖歐拉回路的判斷是用網絡流,實現方法:
首先對所有的無向邊隨便定向,之後會進行調整。然後統計每個點的出入度,如果有某個點出入度之差為奇數,則不存在歐拉回路,因為相差為奇數的話,無論如果調整邊,都不能使得每個點的出入度相等。
現在每個點的出入度之差為偶數了,把這個偶數除以2,得x。則對每個頂點改變與之相連的x條邊的方向就可以使得該點出入度相等。如果每個點都能達到出入度相等,自然就存在歐拉回路了。
現在問題就變成了改變哪些邊的方向能讓每個點出入度相等了,構造網絡流模型。
有向邊不能改變方向,所以不添加有向邊。對於在開始的時候任意定向的無向邊,按所定的方向加邊,容量為1。源點向所有出>入的點連邊,容量為該點的x值;所有入>出的點向匯點連邊,容量為該點的x值。
建圖完成了,求解最大流,如果能滿流分配,則存在歐拉回路。那麼哪些邊改變方向才能得到歐拉回路呢?查看流量分配,所有流量非0的邊就是要改變方向的邊。
原理是因為滿流分配,所以和源點相連的點一定都有x條邊流入,將這些邊反向這些點就出入度相等了,和匯點相連的亦然。沒有和源、匯相連的已經出入度相等了,當然不用修改,至此歐拉回路求解完畢。
我個人的理解: 首先 上面紅色的 是由於 在歐拉回路中每個點的出度都等於入度。 那麼當一個點的出度增加1 那麼入度一定減少1 那麼他們的差依舊保持為偶數 如果不是 說明這個圖不會是歐拉回路
我們把無向邊隨意變成有向邊後 會存在有些點的入度不等於出度 ,那麼我們需要改變以前的那些無向邊中的部分邊 使得每個點都能夠出度等於入度。
如何修改那 ? 按照上面的方式連接各個邊之後 形成的圖的意思 就是: 從s到所有邊中找出部分邊修改使得與s相連的每個點的入度等於出度 同時使得與t相連的每個點的出度等於入度
DINIC算法
#include <stdio.h> #include <string.h> #define VM 222 #define EM 20550 #define inf 0x3f3f3f3f struct Edge { int frm,to,cap,next; }edge[EM]; int head[VM],dep[VM],ep; //dep為點的層次 void addedge (int cu,int cv,int cw) //第一條邊下標必須為偶數 { edge[ep].frm = cu; edge[ep].to = cv; edge[ep].cap = cw; edge[ep].next = head[cu]; head[cu] = ep; ep ++; edge[ep].frm = cv; edge[ep].to = cu; edge[ep].cap = 0; edge[ep].next = head[cv]; head[cv] = ep; ep ++; } int BFS (int src,int des) //求出層次圖 { int que[VM],i,front = 0,rear = 0; memset (dep,-1,sizeof(dep)); que[rear++] = src; dep[src] = 0; while (front != rear) { int u = que[front++]; front = front%VM; for (i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to; if (edge[i].cap > 0&&dep[v] == -1) //容量大於0&&未在dep中 { dep[v] = dep[u] + 1; //建立層次圖 que[rear ++] = v; rear = rear % VM; if (v == des) //找到匯點 返回 return 1; } } } return 0; } int dinic (int src,int des) { int i,res = 0,top; int stack[VM]; //stack為棧,存儲當前增廣路 int cur[VM]; //存儲當前點的後繼 跟head是一樣的 while (BFS(src,des)) //if BFS找到增廣路 { memcpy (cur,head,sizeof (head)); int u = src; //u為當前結點 top = 0; while (1) { if (u == des) //增廣路已全部進棧 { int min = inf,loc ; for (i = 0;i < top;i ++) //找最小的增廣跟並loc記錄其在stack中位置 if (min > edge[stack[i]].cap) //以便退回該邊繼續DFS { min = edge[stack[i]].cap; loc = i; } for (i = 0;i < top;i ++) //偶數^1 相當加1 奇數^1相當減1 當正向邊 = 0&&路徑不合適時,正加負減 { //偶數是正向邊,奇數是負向邊,邊從0開始 edge[stack[i]].cap -= min; edge[stack[i]^1].cap += min; } //將增廣路中的所有邊修改 res += min; top = loc; u = edge[stack[top]].frm; //當前結點修改為最小邊的起點 } for (i = cur[u];i != -1;cur[u] = i = edge[i].next) //找到當前結點對應的下一條邊 if (edge[i].cap != 0&&dep[u] + 1 == dep[edge[i].to])//不滿足條件時,修改cur值(去掉不合適的占)eg:1-->2 1-->3 1-->4 有邊 但只有 break; // 1-->4 這條邊滿足條件 就把1到2、3的邊給去掉 if (cur[u] != -1) //當前結點的下一條邊存在 { stack[top ++] = cur[u]; //把該邊放入棧中 u = edge[cur[u]].to; //再從下個點開始找 } else { if (top == 0) //當前結點無未遍歷的下一條邊且棧空,DFS找不到下一條增廣路 break; dep[u] = -1; //當前結點不在增廣路中,剔除該點 u = edge[stack[--top]].frm; //退棧 回朔,繼續查找 } } } return res; } int in[VM],out[VM]; int abs(int qa) { if(qa>0) return qa; return -qa; } int main() { int T,n,m; int u,v,d; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); ep=0; memset (head,-1,sizeof(head)); memset(out,0,sizeof(out)); memset(in,0,sizeof(in)); while(m--) { scanf("%d%d%d",&u,&v,&d); if(u == v) continue; out[u]++; in[v]++; if(!d) { addedge(u,v,1); } } int flag=0; int sum=0; for(int i=1;i<=n;i++) { if(abs(in[i]-out[i])%2 == 1) { flag=1; break; } if(in[i]<out[i]) { addedge(0,i,(out[i]-in[i])/2); sum += (out[i]-in[i])/2; } else { addedge(i,n+1,(in[i]-out[i])/2); } } if(flag) { printf("impossible\n"); continue; } int ans = dinic(0,n+1); if(sum == ans) printf("possible\n"); else printf("impossible\n"); } return 0; }
#include <stdio.h> #include<string.h> #include<algorithm> using namespace std; #define MAX 210 #define INFINITY 1000000000 struct edge { int cap; int flow; int ver; edge *next; edge *rev; }; edge edges[2500]; edge *link[MAX+1]; int dist[MAX +1]; int h[MAX + 1]; int in[MAX+1]; int out[MAX+1]; int num; int total_flow; int min(int a, int b) { return a < b ? a : b; }; void addedge(int start, int end, int c) { num++; edges[num].ver = end; edges[num].cap = c; edges[num].next = link[start]; link[start] = edges + num; num++; edges[num].ver = start; edges[num].cap = 0; edges[num].next = link[end]; link[end] = edges + num; link[start]->rev = link[end]; link[end]->rev = link[start]; } void rev_bfs(int n, int src, int des) { int q[MAX + 1]; int head = 0; int tail = 0; for(int i = 1; i <= n; i++) { dist[i] = MAX; h[i] = 0; } q[tail++] = des; dist[des] = 0; h[0] = 1; int p; while(tail != head) { p = q[head++]; for(edge *e = link[p]; e; e = e->next) { if(dist[e->ver] != MAX || e->rev->cap == 0) continue; dist[e->ver] = dist[p] + 1; h[dist[e->ver]]++; q[tail++] = e->ver; } } } int sap(int n, int src, int des) { rev_bfs(n, src, des); edge *cur_edges[MAX+1]; edge *rev_path[MAX+1]; total_flow = 0; int i; for(i = 1; i <= n ; i++) cur_edges[i] = link[i]; int argu_flow = INFINITY; int u = src; while(dist[src] < n) { if(u == des) { for(i = src; i != des; i = cur_edges[i]->ver) argu_flow = min(argu_flow, cur_edges[i]->cap); for(i = src; i != des ; i = cur_edges[i]->ver) { cur_edges[i]->cap -= argu_flow; cur_edges[i]->rev->cap += argu_flow; cur_edges[i]->flow += argu_flow; cur_edges[i]->rev->flow -= argu_flow; } total_flow += argu_flow; u = src; } edge *e; for(e = cur_edges[u]; e; e = e->next) if(e->cap > 0 && dist[u] == dist[e->ver] + 1) break; if(e) { cur_edges[u] = e; rev_path[e->ver] = e->rev; u = e->ver; } else { if(--h[dist[u]] == 0) break; cur_edges[u] = link[u]; int mini_dist = n; for(edge *e = link[u]; e; e = e->next) if(e->cap > 0) mini_dist = min(mini_dist, dist[e->ver]); dist[u] = mini_dist + 1; h[dist[u]]++; if(u != src) u = rev_path[u]->ver; } } return total_flow; } int main() { int T,m,n,src,des; int u,v,d; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); num=0; memset(link, 0, sizeof(link)); memset(out,0,sizeof(out)); memset(in,0,sizeof(in)); src=n+1; des=n+2; while(m--) { scanf("%d%d%d",&u,&v,&d); if(u == v) continue; out[u]++; in[v]++; if(!d) { addedge(u,v,1); } } int flag=0; int sum=0; for(int i=1;i<=n;i++) { if(abs(in[i]-out[i])%2 == 1) { flag=1; break; } if(in[i]<out[i]) { addedge(src,i,(out[i]-in[i])/2); sum += (out[i]-in[i])/2; } else { addedge(i,des,(in[i]-out[i])/2); } } if(flag) { printf("impossible\n"); continue; } n=n+2; int ans=sap(n,src,des);//n是點的個數(n+2個) 點下標從1開始到 n+2(des) if(sum == ans) printf("possible\n"); else printf("impossible\n"); } return 0; }