一般图带权多重匹配(欧拉图+最小费用流)

problem

给定 n n n 个数 { a i } \{a_i\} {ai},其中 k k k a i a_i ai 是奇数,再给一个 n × n n\times n n×n 的矩阵 { c i , j } \{c_{i,j}\} {ci,j},无论是 a a a 还是 c c c,都保证是非负整数。

现在可以做一类操作:将 a i − 1 , a j − 1 a_i-1,a_j-1 ai1,aj1,花费 c i , j c_{i,j} ci,j 代价, i , j i,j i,j 可以相同。

要求把所有的 { a i } \{a_i\} {ai} 全都变为 0 0 0

求最小花费,无解输出 − 1 -1 1

n ≤ 50 , k ≤ 8 n\le 50,k\le 8 n50,k8,保证 c i , j = c j , i c_{i,j}=c_{j,i} ci,j=cj,i

solution

显然,每次操作都会使得 ( ∑ a i ) − 2 (\sum a_i)-2 (ai)2。所以如果 ∑ a i \sum a_i ai 是奇数,即 a i a_i ai 为奇数的 i i i 有奇数个,则无解。

接下来再来解决有解的问题。

先考虑 k = 0 k=0 k=0 的弱化版。

那就存在欧拉回路,要求每个点的入度和出度相同,经典网络流模型转化。

将每个点拆成两个点,各放左右部,与源/汇点的连边流量设置为 a i 2 \frac{a_i}2 2ai

花费针对“关系”而言,左右部点之间连边流量无穷带花费。

直接跑最小费用流即可。

转化思考:

如果操作 i , j i,j i,j,就在图上连一条 ( i , j ) (i,j) (i,j) 的边。那么最后这张图可能有重边和若干个环。

发现这是一张欧拉图,存在欧拉回路。我们能找到一种定向方式使得每个点的入度和出度相同。

推出存在一种最优方案使得每个点的入度和出度相同。

将每个点拆成入度点和出度点,转化成匹配问题。

现在有几个特殊点是奇数,欧拉回路不存在,变成存在欧拉路径。

我们先通过若干次操作将这些奇数全都消成偶数,就又转化成了欧拉回路,就可以套用上面的方法。

转化思考:

我们通过对图上进行加边,使得这张图最后仍是存在欧拉回路的图。

由此推出存在一种最优方案使得奇数点的入度和出度只相差 1 1 1

由于 k k k 非常小,我们大可直接状压枚举哪一半的奇数点是入度多 1 1 1,剩下的就肯定是出度多 1 1 1

还是转化成了入度和出度二分图的匹配问题,仍然跑个最小费用流。

最后求个 min ⁡ \min min 就完了。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 200
#define inf 0x7f7f7f7f
struct node { int to, nxt, flow, cost; }E[maxn * maxn];
int head[maxn], dis[maxn], lst[maxn], a[maxn], b[maxn], id[maxn];
int c[maxn][maxn];
bool vis[maxn];
int n, cnt, m, s, t;
queue < int > q;

void addedge( int u, int v, int flow, int cost ) {
    E[++ cnt] = { v, head[u], flow, cost }, head[u] = cnt;
    E[++ cnt] = { u, head[v], 0, - cost }, head[v] = cnt;
}

bool SPFA() {
    memset( lst, -1, sizeof( lst ) );
    memset( dis, 0x7f, sizeof( dis ) );
    dis[s] = 0, q.push( s );
    while( ! q.empty() ) {
        int u = q.front(); q.pop(); vis[u] = 0;
        for( int i = head[u];~ i;i = E[i].nxt ) {
            int v = E[i].to;
            if( dis[v] > dis[u] + E[i].cost and E[i].flow ) {
                dis[v] = dis[u] + E[i].cost; lst[v] = i;
                if( ! vis[v] ) vis[v] = 1, q.push( v );
            }
        }
    }
    return ~ lst[t];
}

int solve() {
    memset( head, -1, sizeof( head ) ), cnt = -1;
    for( int i = 1;i <= n;i ++ )
        for( int j = 1;j <= n;j ++ )
            addedge( i, j + n, inf, c[i][j] );
    for( int i = 1;i <= n;i ++ ) {
        addedge( s, i, a[i] + b[i] >> 1, 0 );
        addedge( i + n, t, a[i] - b[i] >> 1, 0 );
    }
    int ans = 0;
    while( SPFA() ) {
        int flow = inf;
        for( int i = lst[t];~ i;i = lst[E[i ^ 1].to] )
            flow = min( flow, E[i].flow );
        ans += flow * dis[t];
        for( int i = lst[t];~ i;i = lst[E[i ^ 1].to] ) {
            E[i ^ 1].flow += flow;
            E[i].flow -= flow;
        }
    }
    return ans;
}

int main() {
    scanf( "%d", &n ); s = 0, t = n << 1 | 1;
    for( int i = 1;i <= n;i ++ ) {
        scanf( "%d", &a[i] );
        if( a[i] & 1 ) id[m ++] = i;
    }
    for( int i = 1;i <= n;i ++ )
        for( int j = 1;j <= n;j ++ )
            scanf( "%d", &c[i][j] );
    if( m & 1 ) return ! puts("-1");
    int ans = inf;
    for( int i = 0;i < (1 << m);i ++ ) {
        for( int j = 0;j < m;j ++ )
            if( i >> j & 1 ) b[id[j]] = 1;
            else b[id[j]] = -1;
        if( __builtin_popcount( i ) ^ (m >> 1) ) continue;
        ans = min( ans, solve() );
    }
    printf( "%d\n", ans );
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值