Address
Solution
好评!感觉做完这题之后对网络流有了更深的理解。
首先要知道, Dinic 是可以动态加边的。
知道了这一点之后,就非常好做了。
建图方案:源点和汇点
和
,点分成三层,第一层为选手,第二层为志愿,第三层为导师。
(1)如果第
名选手的第
档志愿填写了导师
,则由第
名选手的第
档志愿向第
位导师连边,容量为
。
(2)第
位导师向汇点连容量为
的边,即战队人数上限。
按顺序考虑每一位选手。设当前考虑选手
。由源点向
连容量为
的边。
按顺序枚举志愿
,由选手
向选手
的第
档志愿连容量为
的边,尝试增广。
如果增广成功,则选手
会被志愿
录取,此时由源点开始传输
的流量。
否则继续考虑下一档志愿。
如果枚举完所有的志愿之后还是增广不了,则选手
出局。
这样我们解决了第一问,同时求出选手
加入的导师
,为第二问使用。
对于第二问,枚举选手
,先二分答案
,新建一个二分图。一边是选手,一边是导师。
判定时将二分图清空,并对于
,由选手
向导师
连容量为
的边(如果选手
出局就不要连),然后对于
,如果导师
在
的前
档志愿范围内,则由选手
向导师
连容量为
的边,尝试找增广路,如果增广成功则说明
的排名上升
时不会沮丧。将
向左调整,否则向右调整。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
template <class T>
T Min(T a, T b) {return a < b ? a : b;}
const int N = 205, M = 1e6 + 5, E = 13, INF = 0x3f3f3f3f;
int n, m, b[N], a[N][N], s[N], ecnt, nxt[M], adj[M], go[M], cap[M],
S, T, len, que[M], lev[M], vis[M], QAQ, ans1[N], adjU, adjV, wc[N],
th[N][N][E], cnt[N];
void add_edge(int u, int v, int w, int x)
{
adjU = adj[u]; adjV = adj[v];
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; cap[ecnt] = w;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; cap[ecnt] = x;
}
bool keyijiejuediao()
{
int i;
vis[que[len = 1] = S] = ++QAQ;
lev[S] = 0;
For (i, 1, len)
{
int u = que[i];
Edge(u) if (cap[e] && vis[v] < QAQ)
{
vis[que[++len] = v] = QAQ;
lev[v] = lev[u] + 1;
if (v == T) return 1;
}
}
return 0;
}
int dinic(int u, int flow)
{
if (u == T) return flow;
int res = 0, delta;
Edge(u) if (vis[v] == QAQ && lev[u] < lev[v] && cap[e])
{
delta = dinic(v, Min(cap[e], flow - res));
if (delta)
{
cap[e] -= delta; cap[e ^ 1] += delta;
res += delta; if (res == flow) break;
}
}
if (res != flow) lev[u] = -1;
return res;
}
void work()
{
ecnt = 1;
int i, j, k;
n = read(); m = read();
For (i, 1, m) b[i] = read();
For (i, 1, n) For (j, 1, m) th[i][j][0] = 0;
For (i, 1, n) For (j, 1, m)
if (a[i][j] = read()) th[i][a[i][j]][++th[i][a[i][j]][0]] = j;
For (i, 1, n) s[i] = read(), ans1[i] = m + 1, wc[i] = 0;
S = 1; T = n + m + n * m + 2;
For (i, S, T) adj[i] = 0;
For (i, 1, m) add_edge(1 + n + n * m + i, T, b[i], 0);
For (i, 1, n) For (j, 1, m) if (a[i][j])
add_edge(1 + n + (i - 1) * m + a[i][j], 1 + n + n * m + j, 1, 0);
For (i, 1, n)
{
add_edge(S, 1 + i, 1, 0);
For (j, 1, m)
{
add_edge(1 + i, 1 + n + (i - 1) * m + j, 1, 0);
if (keyijiejuediao())
{
ans1[i] = j; dinic(S, INF); break;
}
else ecnt -= 2, adj[1 + i] = adjU,
adj[1 + n + (i - 1) * m + j] = adjV;
}
}
For (i, 1, n) if (ans1[i] != m + 1)
Edge(1 + n + (i - 1) * m + ans1[i])
if (v > 1 + n + n * m && !cap[e])
{
wc[i] = v - 1 - n - n * m; break;
}
For (i, 1, n) printf("%d ", ans1[i]);
printf("\n");
S = 1; T = 2 + n + m;
For (j, 1, n)
{
int l = 0, r = j - 1;
while (l <= r)
{
int mid = l + r >> 1;
ecnt = 1;
For (i, S, T) adj[i] = 0;
For (i, 1, m) cnt[i] = 0;
For (i, 1, j - mid - 1)
{
if (ans1[i] == m + 1) continue;
add_edge(S, i + 1, 0, 1);
For (k, 1, th[i][ans1[i]][0])
if (wc[i] == th[i][ans1[i]][k])
add_edge(1 + i, 1 + n + wc[i], 0, 1);
else add_edge(1 + i, 1 + n + th[i][ans1[i]][k], 1, 0);
cnt[wc[i]]++;
}
For (i, 1, m) add_edge(1 + n + i, T, b[i] - cnt[i], cnt[i]);
add_edge(S, j + 1, 1, 0);
For (i, 1, m) if (a[j][i] && a[j][i] <= s[j])
add_edge(1 + j, 1 + n + i, 1, 0);
if (keyijiejuediao()) r = mid - 1;
else l = mid + 1;
}
printf("%d ", l);
}
printf("\n");
}
int main()
{
int T = read(); read();
while (T--) work();
return 0;
}