图论:KM算法求解二分图最大权完美匹配

题目链接:点击这里

#include<iostream>//二分图最大权匹配 
#include<cstdio>
#include<queue>
#include<algorithm>
#define N 505
#define M 250005
#define INF 9990365505
#define ll long long
using namespace std;
int n,m,x,y,z,tot,tim;
int pre[N];//记录x的先驱节点 
int visx[N],visy[N];//visx[i]表示左侧第i个节点第几轮访问 

int matchx[N],matchy[N];//matchy[i]表示左边第i个节点与右边第matchy[i]个节点匹配 

struct node{
    
    
	int to,w,next;
}kkk[M];
int head[N];//链式前向星存边 

ll ex[N],ey[N],slack[N];//ex表示左边节点值,ey表示右边节点的值,slack用于维护最小的的点权之和减去边权 

void addedge(int x,int y,int z){
    
    //链式前向星存边 
	tot++;
	kkk[tot].to=y;
	kkk[tot].w=z;
	kkk[tot].next=head[x];
	head[x]=tot;
} 

void modify(int cur){
    
    //修改之前的 匹配方式 
    for (int last,x=cur;x;x=last){
    
    
    	last=matchx[pre[x]]; 
		matchx[pre[x]]=x;
		matchy[x]=pre[x];
	}
}
void bfs(int cur){
    
    
    for(int i=1;i<=n;i++)
        slack[i]=INF,pre[i]=0;//初始化 
    queue<int>q;
    q.push(cur);
    ++tim;
    while(1){
    
    
        while (!q.empty()){
    
    //bfs交错树 
            int u=q.front();
            q.pop();
            visx[u]=tim;//u是第tim轮被访问的 
            for (int i=head[u];i;i=kkk[i].next){
    
    
            	
                int v=kkk[i].to,cost=kkk[i].w;//访问u的相邻节点 
                
                if(visy[v]==tim)
                    continue;//本轮已经被访问过的不需要再次被访问 
                    
                ll mincost=ex[u]+ey[v]-cost;//记录 
                
                if (mincost<slack[v]){
    
    //维护最小点权之和减边权 
                    slack[v]=mincost;
                    pre[v]=u;//v的先驱节点记为u   
                    if (!mincost){
    
    //mincost==0 则连边 
                        visy[v]=tim;//这一轮也访问到了v 
                        if (!matchy[v]){
    
    //左侧第v个节点没有与右侧节点匹配 
                            modify(v);//修改之前的匹配方式,并终止 
                            return;
                        }
                        else q.push(matchy[v]);//否则入队 
                    }
                }
            }
        }
        ll mincost=INF;
        for(int i=1;i<=n;++i){
    
     
            if(visy[i]!=tim){
    
     //本轮没有被访问过 
                mincost=min(mincost,slack[i]);//在交错树的边中寻找顶标和与边权之差最小的边
            }
		} 
        for(int i=1;i<=n;++i){
    
    
            if (visx[i]==tim)//左侧节点减去这个值 
                ex[i]-=mincost;
            if (visy[i]==tim)
                ey[i]+=mincost;//右侧节点加这个值 
			else
                slack[i]-=mincost;//维护更新 
        }
        for(int i=1;i<=n;++i){
    
     
            if( visy[i]!=tim && !slack[i]){
    
    //发生冲突并解决冲突之后,继续匹配 
                visy[i]=tim;//标记为本轮访问的 
                if (!matchy[i]){
    
    //没有匹配过 
                    modify(i);//修改 
                    return;//返回 
                }
                else q.push(matchy[i]);//否则入队 
            }
        }
    }
}
void KM(){
    
    
    for(int i=1;i<=n;++i)
        bfs(i);//对每个点做一遍bfs 
    //做完之后的匹配值就是左右两端节点数的总和 
    ll ans=0;//
    for (int i=1;i<=n;++i)
        ans+=ex[i]+ey[i];
    printf("%lld\n",ans);
    for (int i=1;i<=n;++i)
        printf("%d ",matchy[i]);
    printf("\n");
}
int main(){
    
    
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;++i){
    
    //读入数据 
        scanf("%d%d%d",&x,&y,&z);
        addedge(x,y,z);//加边 
        ex[x]=max(ex[x],(ll)z);//点x的最大权值ex[x]是与它相邻的边的最大的权值 
    }
    KM();//km算法求解 
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45695839/article/details/109425056
今日推荐