从大三准备考研开始,基本就废弃了ACM,然后上次同实验室的叫上我一起参加这个比赛,好为接下来的面试做准备。比赛的时候过了小数据就没去想其他I的解法,坐等各位大神出结题报告。苟然不出所料,今天年一早就把这三题的解法仔细看了一遍,觉得格格取数这个题目还是挺不错的,而且对于最小费用做大流这个算法的一直没有去了解过,正好拿这个题好好学习一下这个算法!
看了一下求最小费用最大流的算法,其实感觉就是一个spfa算法。不过正好,这么久没有练习最短路径算法,趁机学习学习。贴上自己的模板(链接表表示图),以后再来看(第一次发现写博客就是做笔记,最重要的是给自己看!)
struct E{//边的结构体
int to,next,fw,cost;
};
void addedg(int from,int to,int fw,int cost){
edg[nodes].to=to; edg[nodes].fw=fw;edg[nodes].cost=cost;
edg[nodes].next=list[from]; list[from]=nodes++;
edg[nodes].to=from; edg[nodes].fw=0;edg[nodes].cost=-cost;
edg[nodes].next=list[to]; list[to]=nodes++;
}
void spfa(){
int i;
int n=sink;
queue<int> Q;
for(i=0;i<=sink;i++){
dis[i]=INF;
vis[i] =0;
}
dis[src]=0; vis[src]=1;
Q.push(src);
while(!Q.empty()){
int t=Q.front(); Q.pop();
int e;
for(e=list[t]; e!=-1; e=edg[e].next){
int to=edg[e].to;
if(edg[e].fw && dis[to]>dis[t]+edg[e].cost)
{
dis[to] = dis[t]+edg[e].cost;
per[to] = e;
if(!vis[to]){
vis[to]=1;
Q.push(to);
}
}
}
vis[t]= 0;
} }
有了这个模板,最小费用最大流算法没问题了,所以重要的就是如何构图了!看了一些构图方法,但是不知道其原理,不过在这篇博客(http://blog.csdn.net/catalyst1314/article/details/23683553)中看到的解释才明白,orz!!解释如下:
让没有必要的流量直接从源点流向汇点。这样一来,总流量就一定是n*m了。而对于要通过行点和列点的流量是多少呢?经过分析可以发现。如果第i行和第j列都已经取出了一个数(不是a[i][j]时),那么a[i][j]必然是不会被取出的。
现在要解决的问题就是如何保证行点和列点至少流过一次,且不会存在两个已经流过的点在被同时流一次。将行点和列点进行拆分。共2*m个行点和2*n个列点。那么就是源点到m个行点的流量为1,费用为负无穷,n个列点到汇点的流量为1,费用为负无穷。剩下的m个行点的流量为n,费用为0,n个列点的流量为m,费用为0.源点到汇点的流量为n*m,费用为0.同时,我们需要建立一个超级源点,保证总流量一定是n*m。即超级源点到源点的流量是n*m。这样就能保证得到的费用是最小值了,最后的结果就是得到的费用加上(n+m)*负无穷。
可以看出来,如果某个行点和某个列点已经被选择,那么如果存在一个流流过这两个点,那么代价必然是a[i][j],显然不如直接从源点流向汇点。所以,就避免出现第i行和第j列都已经取出了一个数且不是a[i][j]时,取出了a[i][j]这种多余情况。同时,可以得到,负无穷只要能使最大边权+负无穷小于0即可。
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <string.h>
using namespace std ;
#define INF 0x3ffffff
#define MAXE 50500
#define MAXV 500
struct E{
int to,next,fw,cost;
};
E edg[MAXE];
int dis[MAXV],vis[MAXV],list[MAXV],per[MAXV];
int flow,ans,nodes;
int src,sink;
void addedg(int from,int to,int fw,int cost){
edg[nodes].to=to; edg[nodes].fw=fw;edg[nodes].cost=cost;
edg[nodes].next=list[from]; list[from]=nodes++;
edg[nodes].to=from; edg[nodes].fw=0;edg[nodes].cost=-cost;
edg[nodes].next=list[to]; list[to]=nodes++;
}
void BuiltMap(int m,int n){
int i,j;
addedg(src,1,n*m,0);
addedg(sink-1,sink,n*m,0);
for(i=1;i<=m;i++){
addedg(1,i+1,1,-INF);
addedg(1,i+m+1,n,0);
}
for(i=1;i<=n;i++){
addedg(2*m+1+i,sink-1,1,-INF);
addedg(2*m+n+1+i,sink-1,m,0);
}
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
{
int c; scanf("%d",&c);
addedg(1+i,2*m+1+j,1,c);
addedg(1+i,2*m+n+1+j,1,c);
addedg(m+1+i,2*m+1+j,1,c);
addedg(m+1+i,2*m+n+1+j,1,c);
}
addedg(1,sink-1,n*m,0);
}
void spfa(){
int i;
int n=sink;
queue<int> Q;
while(true){
for(i=0;i<=sink;i++){
dis[i]=INF;
vis[i] =0;
}
dis[src]=0; vis[src]=1;
Q.push(src);
while(!Q.empty()){
int t=Q.front(); Q.pop();
int e;
for(e=list[t]; e!=-1; e=edg[e].next){
int to=edg[e].to;
if(edg[e].fw && dis[to]>dis[t]+edg[e].cost)
{
dis[to] = dis[t]+edg[e].cost;
per[to] = e;
if(!vis[to]){
vis[to]=1;
Q.push(to);
}
}
}
vis[t]= 0;
}
}
if(dis[sink] == INF) break;
int e,to;
for(to=sink ; to!=src; to=edg[e^1].to ){
e= per[to];
edg[e].fw -= 1;
edg[e^1].fw +=1;
ans += edg[e].cost;
}
flow+=1;
}
}
int main(){
freopen("data.in","r",stdin);
int n,m,j,c;
scanf("%d",&c);
for(j=1;j<=c;j++)
{
scanf("%d%d",&m,&n);
ans= src =nodes= 0;
sink = (n+m)*2+3;
memset(list,-1,sizeof(list));
BuiltMap(m,n);
spfa();
ans= ans+(n+m)*INF;
printf("Case %d: %d\n",j,ans);
}
return 0;
}
测试数据(没有用大数据去测试了)
3
3 3
1 2 3
3 1 2
2 3 1
5 5
1 2 3 4 5
5 1 2 3 4
4 5 1 2 3
3 4 5 1 2
2 3 4 5 1
3 3
1 1 1
1 100 110
1 100 100
感触:不懂算法真可怕,有时怀疑学算法有多大用,自己高不成低不就的,不过就是喜欢,趁读书的时候,在做点自己喜欢的事情未尝不可!