题解 一般图带权多重匹配

传送门

看起来是个费用流题,但是图不知道该怎么建
发现权值为奇数的点数很少
先考虑没有权值为奇数的点时怎么做

(并不知道怎么想到)
考虑将最优决策下的一个操作 \((i, j)\) 视作一条无向边
那么存在一种给边定向的方法使得每个点的入度等于出度(欧拉回路)
所以我们可以将每个点拆成一个左部点和一个右部点,流量各为 \(\frac{a_i}{2}\)
这样就可以费用流了

然后考虑有权为奇的点怎么做
直接状压额外的1流量在左部还是右部即可
要求在左部和右部的额外流量相等

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 110
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, k;
int a[N], c[N][N];

namespace force{
	int bit[N];
	unordered_map<int, int> mp;
	int dfs(int s) {
		if (!s) return 0;
		if (mp.find(s)!=mp.end()) return mp[s];
		int ans=INF;
		for (int i=1; i<=n; ++i) {
			for (int j=i; j<=n; ++j) {
				if (i!=j) {
					int t1=(s>>bit[i])&3, t2=(s>>bit[j])&3;
					if (t1 && t2) ans=min(ans, dfs(s-(t1<<bit[i])-(t2<<bit[j])+((t1-1)<<bit[i])+((t2-1)<<bit[j]))+c[i][j]);
				}
				else {
					int t=(s>>bit[i])&3;
					if (t>=2) ans=min(ans, dfs(s-(t<<bit[i])+((t-2)<<bit[i]))+c[i][i]);
				}
			}
		}
		return mp[s]=ans;
	}
	void solve() {
		for (int i=1; i<=n; ++i) bit[i]=i<<1;
		int s=0;
		for (int i=1; i<=n; ++i) s+=a[i]<<bit[i];
		printf("%d\n", dfs(s));
	}
}

namespace task{
	bool vis[N];
	int dis[N], back[N], inc[N];
	int head[N], l[N], r[N], sta[N], ecnt, top, s, t, ans=INF;
	struct edge{int to, next, flw, cst;}e[N*N*2];
	inline void add(int s, int t, int f, int w) {e[++ecnt]={t, head[s], f, w}; head[s]=ecnt;}
	bool spfa() {
		memset(dis, 127, sizeof(dis));
		memset(back, -1, sizeof(back));
		dis[s]=0; inc[s]=INF;
		queue<int> q;
		q.push(s);
		int u;
		while (q.size()) {
			u=q.front(); q.pop();
			vis[u]=0;
			for (int i=head[u],v; ~i; i=e[i].next) {
				v=e[i].to;
				if (e[i].flw && dis[u]+e[i].cst<dis[v]) {
					dis[v]=dis[u]+e[i].cst;
					back[v]=i; inc[v]=min(inc[u], e[i].flw);
					if (!vis[v]) q.push(v), vis[v]=1;
				}
			}
		}
		return ~back[t];
	}
	int dinic() {
		int ans=0;
		while (spfa()) {
			ans+=dis[t]*inc[t];
			for (int u=t; u!=s; u=e[back[u]^1].to) {
				e[back[u]].flw-=inc[t];
				e[back[u]^1].flw+=inc[t];
			}
		}
		return ans;
	}
	void solve() {
		for (int i=1; i<=n; ++i) if (a[i]&1) sta[++top]=i;
		int lim=1<<top;
		s=n*2+1, t=n*2+2;
		for (int s=0; s<lim; ++s) {
			if (__builtin_popcount(s)!=top/2) continue;
			for (int i=1; i<=n*2+2; ++i) head[i]=-1; ecnt=1;
			for (int i=1; i<=n; ++i) l[i]=r[i]=a[i]/2;
			for (int i=1; i<=top; ++i) ++((s&(1<<(i-1)))?l[sta[i]]:r[sta[i]]);
			// cout<<"l: "; for (int i=1; i<=n; ++i) cout<<l[i]<<' '; cout<<endl;
			// cout<<"r: "; for (int i=1; i<=n; ++i) cout<<r[i]<<' '; cout<<endl;
			for (int i=1; i<=n; ++i)
				for (int j=1; j<=n; ++j)
					add(i, j+n, INF, c[i][j]), add(j+n, i, 0, -c[i][j]);
			for (int i=1; i<=n; ++i) {
				add(task::s, i, l[i], 0), add(i, task::s, 0, 0);
				add(i+n, t, r[i], 0), add(t, i+n, 0, 0);
			}
			ans=min(ans, dinic());
		}
		printf("%d\n", ans);
	}
}

signed main()
{
	freopen("match.in", "r", stdin);
	freopen("match.out", "w", stdout);

	n=read();
	for (int i=1; i<=n; ++i) k+=(a[i]=read())&1;
	if (k&1) {puts("-1"); return 0;}
	for (int i=1; i<=n; ++i) for (int j=1; j<=n; ++j) c[i][j]=read();
	// force::solve();
	task::solve();
	
	return 0;
}
posted @ 2022-02-11 09:44  Administrator-09  阅读(0)  评论(0编辑  收藏  举报