1.题目描述:点击打开链接
2.解题思路:本题的突破口在于建模,其实关于最大流的问题大多数难点都在建模上。本题只告诉了我们前i行,前i列的和值,让求解整个矩阵。事先可以算出第i行的和值和第i列的和值。然后该怎么办呢?由于每个元素都是1~20之间的,因此如果把所有元素都减去1,那么正好是0·19之间,因此联想到每条边的容量是19。此时行的和值要减去C,列的和值减去R。根据网络流的性质:流入结点的流量等于流出结点的流量。因此可以从此着手构造模型:假设每行对应一个X结点,每列对应一个Y结点,然后增加源点s,汇点t。对于每个结点Xi,从s到Xi连接一条弧,容量是A[i]-C(此时所有的A[i],B[i]均值第i行,第i列的和值);从Yi到t连接一条弧,容量是B[i]-R。对于每个结点(Xi,Yj),从Xi到Yj连接一条弧,容量是19。这样的一个网络便满足了之前题目中的所有性质。接下来只用求出s-t的最大流,那么如果s出发和到达t都满载,说明问题有解。结点Xi->Yj的流量就是格子(i,j)减1的值。本题的一个新知识点是int的最大上界表示:~0U>>1,另一个知识点是采用了链表结构的Dinic算法的写法。
3.代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;
const int INF = ~0U >> 1;//即int的最大值
struct Dinic
{
static const int N = 510, M = 100010;
int head[N];//链表头部,存储以u开始的边序号
int en[M];//记录v
int Next[M];//记录下一条边的序号
int cap[M];//记录容量
int tot;//边数
void clear()
{
memset(head, 0, sizeof(head));
tot = 1;
}
void add(int u, int v, int w)
{
en[++tot] = v;
Next[tot] = head[u];
head[u] = tot;
cap[tot] = w;
}
void Add(int u, int v, int w){ add(u, v, w); add(v, u, 0); }
int d[N], cur[N];
int n, s, t;
bool bfs()
{
queue<int>q;
memset(d, -1, sizeof(d));
d[s] = 0;
q.push(s);
while (!q.empty())
{
int x = q.front(); q.pop();
for (int k = head[x],v; k; k = Next[k])
if (cap[k] && d[v = en[k]]==-1)
{
d[v] = d[x] + 1;
q.push(v);
}
}
return d[t] != -1;
}
int dfs(int x, int y)
{
if (x == t || !y)return y;
int z = y;
for (int&k = cur[x],v; k; k = Next[k])
if (cap[k] && d[v = en[k]] == d[x] + 1)
{
int w = dfs(v, min(cap[k], z));
cap[k] -= w;//流量增大意味着净容量减少
cap[k ^ 1] += w;//反向边容量表示了正向边的流量
z -= w;
if (!z)break;
}
if (z == y)d[x] = -1;
return y - z;
}
int Maxflow(int s, int t)
{
this->s = s, this->t = t;
int flow = 0;
while (bfs())
{
for (int i = 1; i <= n; i++)
cur[i] = head[i];
flow += dfs(s, INF);
}
return flow;
}
}g;
int r[25], c[25], R, C, rnd;
void solve()
{
scanf("%d%d", &R, &C);
for (int i = 1; i <= R; i++)scanf("%d", r + i);
for (int i = 1; i <= C; i++)scanf("%d", c + i);
int s = R + C + 1, t = R + C + 2;
g.n = R + C + 2;
for (int i = 1; i <= R; i++)g.Add(s, i, r[i] - r[i - 1] - C);//r[i]-r[i-1]就是第i行的和值
for (int i = 1; i <= C; i++)g.Add(R + i, t, c[i] - c[i - 1] - R);//同理,注意为了区分行和列的下标,要多加一个R
for (int i = 1; i <= R;i++)
for (int j = 1; j <= C; j++)
g.Add(i, R + j, 19);
g.Maxflow(s, t);
printf("Matrix %d\n", ++rnd);
for (int i = 1; i <= R; i++)
{
vector<int>ans;
for (int k = g.head[i]; k; k = g.Next[k])
if (g.en[k] != g.s)ans.push_back(g.cap[k ^ 1] + 1);//k的反向边的容量表示k的流量
for (int j = ans.size() - 1; j >= 0; j--)//由于新的边位于链表首位,因此要逆序输出
printf("%d%c", ans[j], j ? ' ' : '\n');
}
}
int main()
{
//freopen("t.txt", "r", stdin);
int T;
cin >> T;
while (T--)
{
g.clear();
solve();
if (T)puts("");
}
return 0;
}