该题是一道经典的二分图匹配的题目 。现在终于有点明白什么是二分图匹配了,其实说白了就是依赖于最大流算法之上的一种解决特定问题的算法 。 所谓二分图,就是我们假定有两个集合A和B,每个集合中有若干元素(点),其中源点与A相连,汇点与B相连,并且他们的总容量决定了最终答案的上限,所以一定要维护好 。 然后由A中的点向B中的点连线,他们之间也有一定的容量制约关系(具体看题目中的边权值限制)。这样就可以求出最大流量匹配了。
有时我们要求完美匹配,即所有流入的量等于流出的量 。
该题构思极其巧妙,因为我们已知每一行之和和每一列之和,那么我们将这个实际问题抽象出来就是:将每一行当成与源点相连的结点,将每一列当做与汇点相连的结点,那么这就像水流一样,我们假想有一个从横交错的水管,有水从每一列流出,流到每一列中 。 由于问题本身保证有解,因此这是一个完美匹配,从行中流出的水最终会完全汇入列中。
那么每个行和列的交点流量就是该点的值,因为要求值的范围在1~20,因此我们让行结点和列节点的容量最大为19 ,等等!为什么是19呢? 因为在最大流算法中,我们运用增广路算法让流量从0开始增加,这也就意味着有些边的流量为0,所以我们不妨将所有交点的值减去1,那么容量范围就成了0~19,那么相应的源点到各点的容量以及汇点到各点的容量都会发生相应变化 。
另外对于解的打印,我一开始想在增广路算法中更新,最后发现是不可以的,因为还存在反向流 。
细节参加代码:
#include<bits/stdc++.h> using namespace std; const int INF = 1000000000; const int maxn = 305; int T,n,r,kase=0,c,A[maxn],B[maxn],t,aa[maxn],bb[maxn],m,a[maxn],p[maxn],ans[maxn][maxn]; struct Edge{ int from,to,cap,flow; Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f) {} }; vector<Edge> edges; vector<int> g[maxn]; void init() { for(int i=0;i<maxn;i++) g[i].clear(); edges.clear(); memset(ans,0,sizeof(ans)); } void addedge(int from,int to,int cap) { //增加边并将每个节点对应的边保存在g中 edges.push_back(Edge(from,to,cap,0)); edges.push_back(Edge(to,from,0,0)); t = edges.size(); g[from].push_back(t-2); g[to].push_back(t-1); } int maxflow(int s,int t) { int flow = 0; //最大流初始化为0 for(;;) { //核心算法,需要注意,我们一开始加进来的边的流量都是0,通过求最小残量逐步增广,更新最大流 memset(a,0,sizeof(a)); queue<int> Q; Q.push(s); a[s] = INF; while(!Q.empty()) { int x = Q.front(); Q.pop(); for(int i=0;i<g[x].size();i++) { Edge& e = edges[g[x][i]]; if(!a[e.to]&&e.cap > e.flow) { p[e.to] = g[x][i]; //记录每次增加流量的路径 a[e.to] = min(a[x],e.cap-e.flow); //求出该道路中所有残量的最小值 Q.push(e.to); } } if(a[t]) break; //到达终点,退出 } if(!a[t]) break; //终点残量为0,不能再增广,break; for(int u = t;u != s; u = edges[p[u]].from) { edges[p[u]].flow += a[t];//将所求残量加入到该路径中 edges[p[u]^1].flow -= a[t]; //将反向路径减去 } flow += a[t]; //更新总的最大流 } return flow; } void print() { for(int i=0;i<edges.size();i+=2) { ans[edges[i].from][edges[i].to] = edges[i].flow; } for(int i=1;i<=r;i++) { printf("%d",ans[i][101]+1); for(int j=2;j<=c;j++) printf(" %d",ans[i][j+100]+1); printf("\n"); } } int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&r,&c); init(); for(int i=1;i<=r;i++) scanf("%d",&A[i]); for(int i=1;i<=c;i++) scanf("%d",&B[i]); for(int i=1;i<=r;i++) aa[i] = A[i] - A[i-1]; for(int i=1;i<=c;i++) bb[i] = B[i] - B[i-1]; for(int i=1;i<=r;i++) addedge(0,i,aa[i]-c); for(int i=1;i<=c;i++) addedge(i+100,300,bb[i]-r); for(int i=1;i<=r;i++) //加100,还有0和300都是为了将结点值区别开来 for(int j=1;j<=c;j++) addedge(i,j+100,19); printf("Matrix %d\n",++kase); int cnt = maxflow(0,300); print(); if(T) printf("\n"); } return 0; }