CCPC Camp day2 I.堡堡的宝藏(二分图最大权值匹配,费用流,KM算法)

在这里插入图片描述


矩阵图一定是一个二分图,问题转化成在这个二分图上有 k 条边,要满足任意一条边两端的点权和大于这条边的边权,求最小的点权和。

在这个二分图上,考虑只给 X 部的点赋点权,每一个 X 部的点的点权等于连出去的边的边权的最大值, Y 部的点的点权都是 0。

接下来考虑升高一部分 Y 部的点的点权,使得更多的 X 部的点的点权下降。
例如:
在这里插入图片描述
此时若 Y 升高 d,则右边的 X 都可以减少 d,最终答案变小。

在更复杂的图里难以判断升高某个点的点权是否会使得答案减小,也可以会使得答案增大。

考虑什么情况下可以升高 Y 点的点权:
如果了解 KM 算法,这个模型实际上就是 KM 算法的模型,每个点的点权就是顶标。

KM 算法寻找增广路的方法和 匈牙利算法相同,访问的边的条件多了一条 v a l [ x ] + v a l [ y ] = = w val[x] + val[y] == w val[x]+val[y]==w
因此每一次寻找增广路是在原图中的一个子图中寻找,当某个点无法找到匹配点时扩充子图。

如何扩充子图: 通过修改顶标,使得一些权值更小的边也被考虑进来,就完成了子图扩充。

如何修改顶标:
在寻找增广路时 dfs 遍历会得到一棵匹配边和非匹配边交替的交替树(似乎也叫匈牙利树),将交替树中的 X 部的点集的顶标下降 d,Y 部的点集的顶标升高 d,d 是 x 在交替树上的X集,而边不在交替树上的边的 v a l [ u ] + v a l [ v ] − w val[u] + val[v] - w val[u]+val[v]w 的最小值。

在KM算法中,当无法找到增广路时,所有顶点标号之和会降低: 原因是当找不到增广路时,交替树中X 部的点集大小 s i z e x size_x sizex > Y 部的点集大小 s i z e y size_y sizey s i z e x = s i z e y + 1 size_x = size_y + 1 sizex=sizey+1)。因为交替树中所有 Y 部的点都是已匹配点,对应就有相同数量的 X部 的 点,而当前正在寻找匹配点的 x 就是多出来的那个点。

在求二分图最大权值匹配中,所有顶点的顶标和只在找不到增广路时下降,当整个二分图求得最大权值匹配时,顶标和达到最小值(无法继续下降)。而顶标和的 = 最大权值匹配

求最大权值匹配,可以用费用流(也可以用 KM 算法,KM算法求的是 最大权值完备匹配,这题对所有点建图不一定存在 完备匹配,只对输入边建图一定存在完备匹配但需要处理一下,而费用流可以直接求最大权值匹配,相对来说处理更方便)


具体见代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
#define pii pair<int,int>
#define fir first
#define sec second
typedef long long ll;
const int inf = 0x3f3f3f3f;
int n,m,k;
set<int> tmp;
struct ss{
    int u,v,c,w,nxt;
}edg[maxn];
int head[maxn],cnt,vis[maxn],pre[maxn],d[maxn];
void init() {
    cnt = 0;
    memset(head,-1,sizeof head);
}
void add(int u,int v,int c,int w) {
    edg[cnt].u = u;
    edg[cnt].v = v;
    edg[cnt].c = c;
    edg[cnt].w = w;
    edg[cnt].nxt = head[u];
    head[u] = cnt++;
}
bool spfa(int s,int t) {
    queue<int> q;
    memset(vis,0,sizeof vis);
    memset(d,0x3f,sizeof d);
    memset(pre,-1,sizeof pre);
    q.push(s);
    d[s] = 0;vis[s] = 1;
    while(!q.empty()) {
        int top = q.front();
        q.pop();
        vis[top] = 0;
        for(int i = head[top]; i + 1; i = edg[i].nxt) {
            int v = edg[i].v,c = edg[i].c,w = edg[i].w;
            if(c && d[v] > d[top] + w) {
                d[v] = d[top] + w;pre[v] = i;
                if(!vis[v]) {
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    return pre[t] != -1;
}
int maxflow(int s,int t,ll &cost) {
    int ans = 0;
    cost = 0;
    spfa(s,t);
    while(spfa(s,t)) {
        int mx = inf;
        for(int i = pre[t]; i != -1; i = pre[edg[i ^ 1].v])
            mx = min(mx,edg[i].c);
        for(int i = pre[t]; i != -1; i = pre[edg[i ^ 1].v]) {
            edg[i].c -= mx;
            edg[i ^ 1].c += mx;
            cost += 1ll * mx * edg[i].w;
        }
        ans += mx;
    }  
    return ans;
}
int main() {
    scanf("%d%d%d",&n,&m,&k);
    init();
    for(int i = 1,x1,x2,y1,y2,w; i <= k; i++)    {
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&w);
        int u = (x1 - 1) * m + y1,v = (x2 - 1) * m + y2;
        if((x1 + y1) & 1) swap(u,v);
        add(u,v,1,-w);
        add(v,u,0,w);  
    }
    int s = 0,t = n * m + 1;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            int u = (i - 1) * m + j;
            if((i + j) & 1) {
                add(u,t,1,0);
                add(t,u,0,0);
            }
            add(s,u,1,0);				//注意 X 部和 Y 部 都要和 s 连边,但只有 Y 部 和 t连边
            add(u,s,0,0);
        }
    }
    ll sum = 0;
    maxflow(s,t,sum);
    printf("%lld\n",-sum);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值