题目地址:
https://www.acwing.com/problem/content/2181/
假设有来自 m m m个不同单位的代表参加一次国际会议。每个单位的代表数分别为 r i ( i = 1 , 2 , … , m ) r_i(i=1,2,…,m) ri(i=1,2,…,m)。会议餐厅共有 n n n张餐桌,每张餐桌可容纳 c i ( i = 1 , 2 , … , n ) c_i(i=1,2,…,n) ci(i=1,2,…,n)个代表就餐。为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。试设计一个算法,给出满足要求的代表就餐方案。
输入格式:
第
1
1
1行有
2
2
2个正整数
m
m
m和
n
n
n,
m
m
m表示单位数,
n
n
n表示餐桌数。第
2
2
2行有
m
m
m个正整数,分别表示每个单位的代表数
r
i
r_i
ri。第
3
3
3行有
n
n
n个正整数,分别表示每个餐桌的容量
c
i
c_i
ci。
输出格式:
如果问题有解,在第
1
1
1行输出
1
1
1,否则输出
0
0
0。接下来的
m
m
m行给出每个单位代表的就餐桌号。如果有多个满足要求的方案,只要求输出
1
1
1个方案。
数据范围:
1
≤
m
≤
150
1≤m≤150
1≤m≤150
1
≤
n
≤
270
1≤n≤270
1≤n≤270
1
≤
r
i
,
c
i
≤
100
1≤r_i,c_i≤100
1≤ri,ci≤100
二分图多重匹配,可以用最大流来做。建图方面,可以将 m m m个不同单位看成左部的 m m m个点, n n n个桌子可以看成右部的 n n n个点,最左边加一个源点,并且从源点向左部每个点连一条边,容量就是左部点的那个单位的代表数;再在最右边加一个汇点,并且从右部每个点向汇点连一条边,容量就是右部点的桌子的可容纳的代表数;左部点向右部点连边连满,每条边的容量都是 1 1 1(对应的是每个单位只能派一个代表去某个特定的桌子)。那么,对于任意一种安排的方案,如果某个单位派了人去某个桌子,那么这条边的流量就是 1 1 1,否则就是 0 0 0,所以可以对应一个网络里的可行流;对于任一个整数可行流,看中间的边,谁满了,就说明是哪个单位向哪个桌子派了一个代表。存在可行方案,意味着存在一个流量为 ∑ r i \sum r_i ∑ri的可行流。具体求最大流可以用Dinic算法来做。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 430, M = (150 * 270 + N) * 2, INF = 1e8;
int n, m, S, T;
int h[N], e[M], ne[M], f[M], idx;
int q[N], d[N], cur[N];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx++;
e[idx] = a, ne[idx] = h[b], f[idx] = 0, h[b] = idx++;
}
bool bfs() {
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[tt++] = S, d[S] = 0, cur[S] = h[S];
while (hh < tt) {
int t = q[hh++];
for (int i = h[t]; ~i; i = ne[i]) {
int v = e[i];
if (d[v] == -1 && f[i]) {
d[v] = d[t] + 1;
if (v == T) return true;
cur[v] = h[v];
q[tt++] = v;
}
}
}
return false;
}
int dfs(int u, int limit) {
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {
cur[u] = i;
int v = e[i];
if (d[v] == d[u] + 1 && f[i]) {
int t = dfs(v, min(limit - flow, f[i]));
if (!t) d[v] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic() {
int r = 0, flow;
while (bfs()) while (flow = dfs(S, INF)) r += flow;
return r;
}
int main() {
scanf("%d%d", &m, &n);
S = 0, T = m + n + 1;
memset(h, -1, sizeof h);
int tot = 0;
for (int i = 1; i <= m; i++) {
int c;
scanf("%d", &c);
add(S, i, c);
tot += c;
}
for (int i = 1; i <= n; i++) {
int c;
scanf("%d", &c);
add(m + i, T, c);
}
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
add(i, m + j, 1);
if (dinic() != tot) printf("0\n");
else {
printf("1\n");
// 找一下残留网络里容量为0的边
for (int i = 1; i <= m; i++) {
for (int j = h[i]; ~j; j = ne[j])
if (e[j] > m && e[j] <= m + n && !f[j])
printf("%d ", e[j] - m);
printf("\n");
}
}
return 0;
}
时间复杂度 O ( m n ) O(mn) O(mn),空间 O ( n ) O(n) O(n)。