题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2853
题目大意:二分图匹配费用流。①最大匹配②最小原配变动
解题思路:
如果去掉第二个要求,那么就是裸KM。
然而加上第二个要求,那么就需要一种新的建图方式。
建图
对于输入矩阵,每一条边,cost扩大K倍($K=n+1$)
对于原配,每一条边cost在扩大K倍基础上+1
KM
统计cost时,直接把cost整除K,然后累加。
并且Hash一下原配边的变动情况。
扩大K倍的作用
准确来说,K倍是为了+1而存在的。由于要优先考虑原配边,所以cost要大些。
但是,加大的cost又要使得最后好统计真实的cost。所以选择扩大K倍,然后整除K,这样,+1会被直接舍掉。
K=n+1是防止n=1的情况被卡姿势。
代码
#include "iostream" #include "cstdio" #include "cstring" #include "algorithm" using namespace std; #define maxn 55 int n,m,Hash[maxn],tmp,old,K; int link[maxn],LX[maxn],RX[maxn],W[maxn][maxn],slack[maxn],e[maxn][maxn]; bool S[maxn],T[maxn]; const int inf=0x3f3f3f3f; bool dfs(int u) { S[u]=true; for(int v=1;v<=m;v++) { if(T[v]) continue; int t=LX[u]+RX[v]-W[u][v]; if(!t) { T[v]=true; if(!link[v]||dfs(link[v])) { link[v]=u; return true; } } else if(t<slack[v]) slack[v]=t; } return false; } void update() { int a=inf; for(int i=1;i<=m;i++) //ny if(!T[i]&&slack[i]<a) a=slack[i]; for(int i=1;i<=n;i++) //nx if(S[i]) LX[i]-=a; for(int i=1;i<=m;i++) //ny { if(T[i]) RX[i]+=a; else slack[i]-=a; } } void KM() { memset(link,0,sizeof(link)); memset(RX,0,sizeof(RX)); for(int i=1;i<=n;i++) //nx for(int j=1;j<=m;j++) //ny LX[i]=max(LX[i],W[i][j]); for(int i=1;i<=n;i++) //nx { for(int j=1;j<=m;j++) //ny slack[j]=inf; while(1) { memset(S,0,sizeof(S)); memset(T,0,sizeof(T)); if(dfs(i)) break; else update(); } } int res=0,change=0; for(int i=1;i<=m;i++) //ny { if(link[i]) { res+=(W[link[i]][i]/K); if(Hash[i]!=link[i]) change++; } } printf("%d %d\n",change,res-old); } int main() { //freopen("in.txt","r",stdin); while(scanf("%d%d",&n,&m)!=EOF) { memset(W,0,sizeof(W)); memset(Hash,0,sizeof(Hash)); old=0;K=n+1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { scanf("%d",&W[i][j]); W[i][j]*=K; } for(int i=1;i<=n;i++) { scanf("%d",&tmp); old+=(W[i][tmp]/K); Hash[tmp]=i; W[i][tmp]++; } KM(); memset(slack,0,sizeof(slack)); memset(LX,0,sizeof(LX)); } return 0; }
时间: 2024-10-13 06:07:16