传送门:http://www.ifrog.cc/acm/problem/1055
思路:
跑一发floyd,然后再用km。
但是问题来了,这个有可能n != m。那怎么办?
其实可以补上一些不存在的点。来使得n = m。他们的权值就设置为0就好了。意思就是这些人的搭配,是对答案没有贡献的。注意不能设置为-inf。因为补上的那些点也是必须要选人的,只不过他们选了人,相当于没选而已(权值不存在。)如果设置为-inf的那些,那么他们就会把答案改了。
还有一个小trick的就是,一开始,我是把本来地图上的-1的那些点,更改为inf的,inf表示不连通,那么直接floyd就可以了不用特判那么多。那么问题又来了。如果跑了floyd后,还是不连通,那怎么办?他们的权值可是inf啊。组成新图的时候,同时也是需要把他们的权值设置为0的,也就是相当于没选。
或者直接跑费用流省去建虚点的麻烦
下面给出一些概念:
1.通俗地说,完全匹配只要一边的点都在匹配中就行。而完美匹配则是相当于要“双向的完全匹配”。
2.KM算法求的是完美匹配中权最大的一个,在本来图就达不到完美匹配,而只有若干完备匹配的情况下,KM不能求出完备匹配中权最大的一个。
3.如果本来图就达不到完美匹配,但仍要求出最大权匹配,那么该怎么办?
(1)建虚点虚边,只要把所有没有的边设成0就行了,然后跑KM算法
(2)用费用流来跑,这种情况用费用来跑相对来说代码少点,但是效率比KM稍慢
代码一:
(费用流 266ms)
#include <bits/stdc++.h>
using namespace std;
//最小费用最大流,求最大费用只需要取相反数结果即可。
//点的总数为 N,点的编号 0~N
const int maxn=205;
const int maxm=100005;
const int inf=0x3f3f3f3f;
struct Edge{
int to,next,cap,flow,cost;
}es[maxm];
int head[maxn],tol;
int p[maxn];//记录增广路径上 到达点i的边的编号
int d[maxn];//单位总费用
bool vis[maxn];
int N;//节点总个数
int dp[maxn][maxn];
int n, m;
void init(int n){
N=n;
tol=0;
memset(head, -1, sizeof(head));
}
void addedge(int u,int v,int cap,int cost){
es[tol].to=v;
es[tol].cap=cap;
es[tol].cost=cost;
es[tol].flow=0;
es[tol].next=head[u];
head[u]=tol++;
es[tol].to=u;
es[tol].cap=0;
es[tol].cost=-cost;
es[tol].flow=0;
es[tol].next=head[v];
head[v]=tol++;
}
bool spfa(int s,int t){//寻找花销最少的路径
//跑一遍SPFA 找s——t的最少花销路径 且该路径上每一条边不能满流
//若存在 说明可以继续增广,反之不能
queue<int>q;
for(int i=0; i<=N; i++){
d[i]=inf;
vis[i]=false;
p[i]=-1;
}
d[s]=0;
vis[s]=true;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=false;
for(int i=head[u]; i!=-1; i=es[i].next){ //逆向枚举以u为起点的边
int v=es[i].to;
if(es[i].cap>es[i].flow && d[v]>d[u]+es[i].cost){//可以松弛 且 没有满流
d[v]=d[u]+es[i].cost;
p[v]=i; //记录前驱边 的编号
if(!vis[v]){ vis[v]=true; q.push(v);}
}
}
}
return p[t]!=-1;//可达返回true
}
//返回的是最大流,cost存的是最小费用
int MCMF(int s,int t,int &cost){
int flow = 0;cost = 0;
while(spfa(s,t)){//每次寻找花销最小的路径
int Min=inf;
//通过反向弧 在源点到汇点的最少花费路径 找最小增广流
for(int i=p[t]; i!=-1; i=p[es[i^1].to]){
if(Min>es[i].cap-es[i].flow)
Min=es[i].cap-es[i].flow;
}
//增广
for(int i=p[t]; i!=-1; i=p[es[i^1].to]){
es[i].flow+=Min;
es[i^1].flow-=Min;
cost+=es[i].cost*Min;//增广流的花销
}
flow+=Min;//总流量累加
}
return flow;
}
void floyd(){
for(int k=0; k<n+m; k++)
for(int i=0; i<n+m; i++)
for(int j=0; j<n+m; j++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}
int main(){
int T;
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &m);
for(int i=0; i<n+m; i++)
for(int j=0; j<n+m; j++){
scanf("%d", &dp[i][j]);
if(dp[i][j] == -1)dp[i][j] = inf;
}
floyd();
init(n+m+2);
for(int i=0; i<n; i++)
addedge(n+m, i, 1, 0);//源点与男生之间建边
for(int i=n; i<n+m; i++)
addedge(i, n+m+1, 1, 0);//汇点与女生之间建边
for(int i=0; i<n; i++)
for(int j=n; j<n+m; j++)
if(dp[i][j] < inf)
addedge(i, j, 1, -dp[i][j]);//男生与女生之间距离小于inf建边
int cost;
MCMF(n+m, n+m+1, cost);
printf("%d\n", -cost);
}
return 0;
}
代码二:
(KM 126ms)
#include <bits/stdc++.h>
using namespace std;
#define mst(ss,b) memset(ss,b,sizeof(ss));
/* KM算法
* 复杂度O(nx*nx*ny)
* 求最大权匹配
* 若求最小权匹配,可将权值取相反数,结果取相反数
* 点的编号从1开始
*/
const int N=105;
const int inf=0x3f3f3f3f;
int nx,ny;//两边的点数
int g[N][N];//二分图描述
int link[N],lx[N],ly[N];//y中各点匹配状态,x,y中的顶点标号
int slack[N];
bool visx[N],visy[N];
int dp[2*N][2*N];
int n, m;
bool dfs(int x){
visx[x]=true;
for(int y=1; y<=ny; y++){
if(visy[y])continue;
int tmp=lx[x]+ly[y]-g[x][y];
if(tmp==0){
visy[y]=true;
if(link[y]==-1 || dfs(link[y])){
link[y]=x;
return true;
}
}
else if(slack[y]>tmp) slack[y]=tmp;
}
return false;
}
int KM(){
memset(link, -1, sizeof(link));
memset(ly, 0, sizeof(ly));
for(int i=1; i<=nx; i++){
lx[i]=-inf;
for(int j=1; j<=ny; j++)
if(g[i][j]>lx[i])
lx[i]=g[i][j];
}
for(int x=1; x<=nx; x++){
for(int i=1; i<=ny; i++) slack[i]=inf;
while(true){
memset(visx, false, sizeof(visx));
memset(visy, false, sizeof(visy));
if(dfs(x))break;
int d=inf;
for(int i=1; i<=ny; i++)
if(!visy[i] && d>slack[i])
d=slack[i];
for(int i=1; i<=nx; i++)
if(visx[i])
lx[i]-=d;
for(int i=1; i<=ny; i++){
if(visy[i])ly[i]+=d;
else slack[i]-=d;
}
}
}
int res=0;
for(int i=1; i<=ny; i++)
if(link[i]!=-1)
res+=g[link[i]][i];
return res;
}
void floyd(){
for(int k=0; k<n+m; k++)
for(int i=0; i<n+m; i++)
for(int j=0; j<n+m; j++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}
int main(){
int T;
scanf("%d", &T);
while(T--){
mst(dp, 0);
scanf("%d%d", &n, &m);
for(int i=0; i<n+m; i++)
for(int j=0; j<n+m; j++){
scanf("%d", &dp[i][j]);
if(dp[i][j] == -1)dp[i][j] = inf;
}
floyd();
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++){
g[i][j]=dp[i-1][n+j-1];
if(g[i][j] >= inf)g[i][j]=0;
}
if(n < m){
for(int i=n+1; i<=m; i++)
for(int j=1; j<=m; j++)
g[i][j]=0;
}
else if(n > m){
for(int i=1; i<=n; i++)
for(int j=m+1; j<=n; j++)
g[i][j]=0;
}
nx = ny = max(n, m);
printf("%d\n", KM());
}
return 0;
}