はじめに
atcoderの過去問を解いている際に、最大二部マッチングに帰着できる問題に遭遇した。
C - 2D Plane 2N Points
(なお、この問題自体はグラフ理論の知識無しでも工夫すれば解ける問題だったので、気になった方は考えてみてほしい)
競技プログラミングでは最大二部マッチング問題は頻出らしいのだが、自分は初心者に毛が生えた程度の実力なので、最大二部マッチング問題であることには気付いたものの対応するデータ構造、アルゴリズムを用意していなかった。
典型問題だしググれば出てくるだろうと思ったのだが、やはり競技プログラミングでC#erは圧倒的に少数派らしく、コピペで使用できそうなソースコードを見つけることができなかったので、自分で実装したものを残しておくことにした。
最大二部マッチング問題は、最大フロー問題の特殊な場合として扱える。以下の記事が参考になる。
実世界で超頻出!二部マッチング (輸送問題、ネットワークフロー問題)の解法を総整理!
今回使用するDinic法というものは最大フロー問題を高速に解くことができるアルゴリズムで、Ford-Fulkerson法を改善したもの。アルゴリズムの動作原理は以下の記事が詳しい。
tkw's diary - Dinic法
ソースコード
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;namespaceHoge{classDinic{publicDinic(intnode_size){V=node_size;G=Enumerable.Repeat(newList<Edge>(),V).ToList();level=Enumerable.Repeat(0,V).ToList();iter=Enumerable.Repeat(0,V).ToList();}classEdge{publicEdge(intto,intcap,intrev){To=to;Cap=cap;Rev=rev;}publicintTo{get;set;}publicintCap{get;set;}publicintRev{get;set;}}List<List<Edge>>G;intV;List<int>level;List<int>iter;publicvoidAddEdge(intfrom,intto,intcap){G[from].Add(newEdge(to,cap,G[to].Count));G[to].Add(newEdge(from,0,G[to].Count-1));}publicintMaxFlow(ints,intt){intflow=0;while(true){BFS(s);if(level[t]<0){returnflow;}iter=Enumerable.Repeat(0,V).ToList();varf=DFS(s,t,1000000007);while(f>0){flow+=f;f=DFS(s,t,1000000007);}}}voidBFS(ints){level=Enumerable.Repeat(-1,V).ToList();level[s]=0;varque=newQueue<int>();que.Enqueue(s);while(que.Count!=0){varv=que.Dequeue();for(inti=0;i<G[v].Count;i++){vare=G[v][i];if(e.Cap>0&&level[e.To]<0){level[e.To]=level[v]+1;que.Enqueue(e.To);}}}}intDFS(intv,intt,intf){if(v==t)returnf;for(inti=iter[v];i<G[v].Count;i++){iter[v]=i;vare=G[v][i];if(e.Cap>0&&level[v]<level[e.To]){vard=DFS(e.To,t,Math.Min(f,e.Cap));if(d>0){e.Cap-=d;G[e.To][e.Rev].Cap+=d;returnd;}}}return0;}}}
使用法
有向グラフの辺の数だけAddEdgeを呼び出し、辺の始点、終点、重みを与えてやればよいです。MaxFlowが最大フローを返します。
注意点
このソースコードは辺の重みがint型の範囲内のものでしか使用できません。辺の重みがlong型やdouble型の問題に利用したい場合は、EdgeクラスのCapプロパティに対応する部分を書き換える必要があります。C#のジェネリックは数値型のみというような制約が現状ではできないようです。にしてももうちょいうまいやり方なかったのか。decimalとか使えばいいのか?わからん。
また、深さ優先探索で十分に大きい値として用いている1000000007も、場合によっては修正してやる必要があります。
最後に
C#は競プロでの利用者少ないし速度もそんな早くないしでなかなかつらいんだけど、やっぱり好きな言語なのでこれからも使っていきたいです。この記事がC#erの助けになったら嬉しいな。
間違い、改善点等ありましたらコメント等でご指摘いただけると幸いです。