题意:求混合图的欧拉路径。
一句话总结:网络流,最主要在于建图,此题是将出度则是和流量联系在了一起,用最大流来调整边的指向。
分析:
这题的困难之处在于无向边只能用一次,相当于一个方向未定的有向边。
首先用并查集判断图的连通性,(直接计数O(1),做1395 Slim Span学到的技巧)。
我们知道有向图的欧拉路径存在的充要条件是最多两个点的入度不等于出度,而且相差为1。这题要求回路,只需要所有点的入度等于出度就行了。
对于无向边,一开始可以随意确定一个方向。这样不能保证所有点的入度等于出度,但是可以想办法调整。
比如说u->v,那么如果改变方向的话,就相当于把一个出度运输给了v。
这让人联想到了网络流,一条无向边就对应着一条容量为1的边,建图的时候就把u在v直接连一条容量为1的边。
那么剩下的就是决定怎么运输?运输多少?我们的目标是调整使得所有的点入度等于出度。
因为每次调整入度和出度的差改变都为偶,那么如果有入度和出度相差为奇数的点,那么一定是不满足条件的。
下面只考虑度数差为偶的点,对于入度大于出度的点,那么这个点需要出度,需要(in[u]-out[u])/2个出度,那么就把它和汇点连一条相应容量的边。
对于出度大的点类似处理。跑网络流,进行调整。根据残流网络,可以得出边的指向。在跑Euler路径就行了。
#include<bits/stdc++.h> using namespace std; int V,E; const int maxv = 105; #define PB push_back vector<int> D[maxv]; int in[maxv],out[maxv]; struct Edge { int v,cap; }; vector<Edge> edges; vector<int> G[maxv]; int S,T; int vcnt; void AddEdge(int u,int v,int c) { G[u].PB(edges.size()); edges.PB({v,c}); G[v].PB(edges.size()); edges.PB({u,0}); } int lv[maxv]; int q[maxv]; bool bfs() { memset(lv,0,sizeof(int)*(vcnt)); int l = 0, r = 0; q[r++] = S; lv[S] = 1; while(r>l){ int u = q[l++]; for(int i = 0; i < G[u].size(); i++){ Edge &e = edges[G[u][i]]; if(!lv[e.v] && e.cap){ lv[e.v] = lv[u]+1; q[r++] = e.v; } } } return lv[T]; } int cur[maxv]; int dfs(int u,int a) { if(u == T||!a) return a; int flow = 0,f; for(int &i = cur[u]; i < G[u].size(); i++){ Edge &e = edges[G[u][i]]; if(lv[e.v] == lv[u]+1 && (f = dfs(e.v,min(e.cap,a)))){ flow += f; a -= f; e.cap -= f; edges[G[u][i]^1].cap+=f; if(!a) break; } } return flow; } const int INF = 0x3f3f3f3f; int MaxFlow() { int flow = 0; while(bfs()){ memset(cur,0,sizeof(int)*(vcnt)); flow += dfs(S,INF); } return flow; } int build() { int del = 0; for(int i = 1; i <= V; i++){ int d; if((in[i]+out[i])&1) return -1; if(out[i]<in[i]) { d = (in[i]-out[i])>>1; del+=d; AddEdge(i,T,d); }else if(in[i]<out[i]){ d = (out[i]-in[i])>>1; AddEdge(S,i,d); } } return del; } int pa[maxv]; int Find(int x) { return x == pa[x]?x:pa[x]=Find(pa[x]); } bool vis[maxv]; int cnt; void init() { S = 0; T = V+1; vcnt = T+1; for(int i = 1; i <= V; i++) D[i].clear(),in[i]=out[i]=0; edges.clear(); for(int i = 0; i <= T; i++) G[i].clear(); for(int i = 1; i <= V; i++) pa[i] = i; memset(vis,0,sizeof(bool)*vcnt); cnt = 0; } bool read() { scanf("%d%d",&V,&E); init(); for(int i = 0; i < E; i++){ char ch; int u,v; scanf("%d %d %c",&u,&v,&ch); if(ch == 'U'){ AddEdge(u,v,1); }else { D[u].PB(v); } if(!vis[u]) cnt++,vis[u] = true; if(!vis[v]) cnt++,vis[v] = true; int s1 = Find(u),s2 = Find(v); if(s1 != s2) { cnt--; pa[s1] = s2; } out[u]++; in[v]++; } return cnt == 1; } void reBuild() { for(int u = 1; u <= V; u++){ for(int i = 0; i < G[u].size(); i++){ Edge &e = edges[G[u][i]]; if(e.cap) { int v0 = edges[G[u][i]^1].v, v1 = e.v; if(v0&&v0<=V&&v1&&v1<=V) D[v0].PB(v1); } } } } stack<int> ans; void Euler(int u) { for(int &i = cur[u]; i < D[u].size();){ int v = D[u][i++]; Euler(v); //printf("%d ",v); ans.push(v); } } void solve() { if(read()) { int totFlow = build(); if(~totFlow && totFlow <= MaxFlow()) { reBuild(); memset(cur,0,sizeof(int)*(vcnt)); Euler(1); printf("1"); while(ans.size()){ printf(" %d",ans.top()); ans.pop(); } putchar(' '); return; } } puts("No euler circuit exist"); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int Test; scanf("%d",&Test); while(Test--){ solve(); if(Test) putchar(' '); } return 0; }
PS:
欧拉路径输出的套圈算法,进行了一点小小的优化,从dinic中收到启发改成了用数组cur[],这样可以节省判断vis的时间。
还有一个问题,此题是SJ,如下直接在dfs里逆序输出就WA。。。保存答案stack里输出才A,这样写有什么trick的情况吗?
void Euler(int u) { for(int &i = cur[u]; i < D[u].size();){ int v = D[u][i++]; Euler(v); printf("%d ",v); } } 调用 memset(cur,0,sizeof(int)*(vcnt)); Euler(1); printf("1 ");