problem
给定 nnn 个数 {ai}\{a_i\}{ai},其中 kkk 个 aia_iai 是奇数,再给一个 n×nn\times nn×n 的矩阵 {ci,j}\{c_{i,j}\}{ci,j},无论是 aaa 还是 ccc,都保证是非负整数。
现在可以做一类操作:将 ai−1,aj−1a_i-1,a_j-1ai−1,aj−1,花费 ci,jc_{i,j}ci,j 代价,i,ji,ji,j 可以相同。
要求把所有的 {ai}\{a_i\}{ai} 全都变为 000。
求最小花费,无解输出 −1-1−1。
n≤50,k≤8n\le 50,k\le 8n≤50,k≤8,保证 ci,j=cj,ic_{i,j}=c_{j,i}ci,j=cj,i。
solution
显然,每次操作都会使得 (∑ai)−2(\sum a_i)-2(∑ai)−2。所以如果 ∑ai\sum a_i∑ai 是奇数,即 aia_iai 为奇数的 iii 有奇数个,则无解。
接下来再来解决有解的问题。
先考虑 k=0k=0k=0 的弱化版。
那就存在欧拉回路,要求每个点的入度和出度相同,经典网络流模型转化。
将每个点拆成两个点,各放左右部,与源/汇点的连边流量设置为 ai2\frac{a_i}22ai。
花费针对“关系”而言,左右部点之间连边流量无穷带花费。
直接跑最小费用流即可。
转化思考:
如果操作 i,ji,ji,j,就在图上连一条 (i,j)(i,j)(i,j) 的边。那么最后这张图可能有重边和若干个环。
发现这是一张欧拉图,存在欧拉回路。我们能找到一种定向方式使得每个点的入度和出度相同。
推出存在一种最优方案使得每个点的入度和出度相同。
将每个点拆成入度点和出度点,转化成匹配问题。
现在有几个特殊点是奇数,欧拉回路不存在,变成存在欧拉路径。
我们先通过若干次操作将这些奇数全都消成偶数,就又转化成了欧拉回路,就可以套用上面的方法。
转化思考:
我们通过对图上进行加边,使得这张图最后仍是存在欧拉回路的图。
由此推出存在一种最优方案使得奇数点的入度和出度只相差 111。
由于 kkk 非常小,我们大可直接状压枚举哪一半的奇数点是入度多 111,剩下的就肯定是出度多 111。
还是转化成了入度和出度二分图的匹配问题,仍然跑个最小费用流。
最后求个 min\minmin 就完了。
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;
}