[二分图最大权值匹配KM模板] 牛客2021暑假第五场J

题目

题目

思路

把时间和物品进行二分图最大匹配,因为答案求的最小值,所以是负值情况下的最大值

代码

BFS O ( n 3 ) \text{BFS}O(n^3) BFSO(n3)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 310;
int x,y,z,v;
int w[N][N];//边权
int la[N], lb[N];//左、右部点的顶标
bool va[N], vb[N];//访问标记,是否在交错树中
int match[N];//右部点匹配的左部点(一个只能匹配一个嘛)
int n;
int delta, upd[N];
int p[N];
int c[N];

void bfs(int x)
{
    int a, y = 0, y1 = 0;

    for(int i = 1; i <= n; ++ i)
        p[i] = 0, c[i] = INF;
    match[y] = x;
    do{
        a = match[y], delta = INF, vb[y] = true;
        for(int b = 1; b <= n; ++ b){
            if(!vb[b]){
                if(c[b] > la[a] + lb[b] - w[a][b])
                    c[b] = la[a] + lb[b] - w[a][b], p[b] = y;
                if(c[b] < delta)//差值还是取最小的
                    delta = c[b], y1 = b;
            }
        }
        for(int b = 0; b <= n; ++ b)
            if(vb[b])
                la[match[b]] -= delta, lb[b] += delta;
            else c[b] -= delta;
        y = y1;
    }while(match[y]);
    
    while(y) match[y] = match[p[y]], y = p[y];
}

int KM()
{
    for(int i = 1; i <= n; ++ i)
        match[i] = la[i] = lb[i] = 0;
    for(int i = 1; i <= n; ++ i){
        for(int j = 1; j <= n; ++ j)
            vb[j] = false;
        bfs(i);
    }
    int res = 0;
    for(int y = 1; y <= n; ++ y)//若匹配失败w[match[y]][y]=INF;
        if(w[match[y]][y]!=INF)
		res += w[match[y]][y];
    return res;
}
int d(int x,int y,int z,int v,int t){
	return x*x+y*y+(z+v*t)*(z+v*t);
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x>>y>>z>>v;
		for(int j=1;j<=n;j++){
			w[i][j]=-d(x,y,z,v,j-1);
		//	cout<<x[i]<<" "<<y[i]<<" "<<z[i]<<" "<<love[i][j]<<endl;
		}
	}
	cout<<-1*KM()<<endl;
    return 0;
}



DFS O ( n 4 ) 板 子 \text{DFS}O(n^4)板子 DFSO(n)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define IO ios::sync_with_stdio(false)

typedef long long ll;

using namespace std;
const int INF = 0x3f3f3f3f;
const int mn=305;
const ll mod=1000000007;
int match[mn],lx[mn],ly[mn],slack[mn],fa[mn*3];
int G[mn][mn];
bool visx[mn],visy[mn];
int n,nx,ny;

int findpath(int x)
{
    int tempDelta;
    visx[x]=true;
    for(int y=1;y<=ny;y++){
        if(visy[y])continue;
        tempDelta =lx[x]+ly[y]-G[x][y];
        if(tempDelta ==  0){
            visy[y] = true;
            fa[y+nx]=x;
            if(match[y] == -1){
                return y+nx;
            }
            fa[match[y]]=y+nx;//记录交替树的父亲信息(为了区别X,Y集合,Y的点都映射成n+y)
            int res=findpath(match[y]);
            if(res>0)return res;//返回增广路的末端叶子节点
        }
        else if(slack[x] > tempDelta)//统计以x为准的slack值。
            slack[x] = tempDelta;
    }
    return -1;
}
void KM()
{
    for(int x = 1 ; x <= nx ; ++x){
       for(int i = 1 ; i <= nx ; ++i) slack[i] =INF;
       for(int i=1;i<=nx+ny;i++)fa[i]=-1;
        memset(visx,false,sizeof(visx));
        memset(visy,false,sizeof(visy));//换到外面,可以保留原树
        int fir=1;int leaf=-1;
        while(true){
            if(fir==1){
                leaf=findpath(x);
                fir=0;
            }
            else{
                for(int i=1;i<=nx;i++){
                    if(slack[i]==0){//只接着搜有新边加入的X点
                        slack[i]=INF;//slack要重新清空,方以后接着用
                        leaf=findpath(i);
                        if(leaf>0)break;
                    }
                }
            }
            if(leaf>0){
                int p=leaf;
                while(p>0){
                    match[p-nx]=fa[p];
                    p=fa[fa[p]];//顺着记录一路找找上去
                }
                break;
            }
            else{
                int delta =INF;
                for(int i = 1 ; i <= nx ; ++i)
                    if(visx[i] && delta > slack[i])
                        delta = slack[i];
                for(int i = 1 ; i <= nx ; ++i)
                    if(visx[i]) {lx[i] -= delta;slack[i]-=delta;}//X点的slack要响应改变,slack变0说明有新边加入
                for(int j = 1 ; j <= ny ; ++j){
                    if(visy[j])
                        ly[j] += delta;
                }
            }
        }
    }
}

int solve()
{   //初始化: 
    memset(match,-1,sizeof(match));
    memset(ly,0,sizeof(ly));
    memset(fa,0,sizeof(fa));
    for(int i = 1 ; i <= nx ; ++i){
        lx[i] = -1;
        for(int j = 1 ; j <= ny ; ++j)
        if(lx[i] < G[i][j])
            lx[i] = G[i][j];
    }
    KM();
    int ans=0;
    for(int i=1;i<=ny;++i)
    {
        if(match[i]!=-1) ans+=G[match[i]][i];
    } 
    return ans;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        nx=n;ny=n;//nx左部图点数,ny右部图点数
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                scanf("%d",&G[i][j]);
            }
        }
        int ans=solve();
        printf("%d\n",ans);
    }
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
KM算法,全称Kuhn-Munkres算法,是一种用于求解二分图的最佳匹配的算法。它可以找到一个匹配,使得两个集合内的所有顶点能够一一匹配,并且获得的权值最大或最小。KM算法在求解带权二分图匹配时,融合了匈牙利算法的思想。算法的步骤如下: 1. 初始化:将两个集合内的顶点分别标记为未被匹配状态。 2. 根据特定的规则,遍历第一个集合内的顶点。 3. 对于每个选中的顶点,遍历第二个集合内的顶点,找到与其相连的较优边。较优边的选择可以根据具体情况而定,可以是较大的权值或者较小的权值。 4. 如果找到了满足条件的边,判断该边对应的第二个顶点是否已经被匹配。如果该顶点还未被匹配,则直接将其与第一个顶点进行匹配。 5. 如果该顶点已经被匹配,但是与其匹配的顶点还可以找到其他的可匹配顶点,则将该顶点重新匹配给第一个顶点。 6. 循环执行步骤2-5,直到无法找到满足条件的边。 通过这样的循环匹配KM算法能够找到二分图的最佳匹配。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [二分图的完全匹配---KM算法](https://blog.csdn.net/li13168690086/article/details/81557890)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值