[BZOJ5251][九省联考2018]劈配(网络流, Dinic 动态加边)

Address

洛谷P4382
BZOJ5251
LOJ#2477

Solution

好评!感觉做完这题之后对网络流有了更深的理解。
首先要知道, Dinic 是可以动态加边的。
知道了这一点之后,就非常好做了。
建图方案:源点和汇点 S S S T T T ,点分成三层,第一层为选手,第二层为志愿,第三层为导师。
(1)如果第 i i i 名选手的第 j j j 档志愿填写了导师 k k k ,则由第 i i i 名选手的第 j j j 档志愿向第 k k k 位导师连边,容量为 1 1 1
(2)第 i i i 位导师向汇点连容量为 b i b_i bi 的边,即战队人数上限。
按顺序考虑每一位选手。设当前考虑选手 i i i 。由源点向 i i i 连容量为 1 1 1 的边。
按顺序枚举志愿 j j j ,由选手 i i i 向选手 i i i 的第 j j j 档志愿连容量为 1 1 1 的边,尝试增广。
如果增广成功,则选手 i i i 会被志愿 j j j 录取,此时由源点开始传输 1 1 1 的流量。
否则继续考虑下一档志愿。
如果枚举完所有的志愿之后还是增广不了,则选手 i i i 出局。
这样我们解决了第一问,同时求出选手 i i i 加入的导师 b e l i bel_i beli ,为第二问使用。
对于第二问,枚举选手 i i i ,先二分答案 m i d mid mid ,新建一个二分图。一边是选手,一边是导师。
判定时将二分图清空,并对于 1 ≤ j ≤ i − m i d − 1 1\le j\le i-mid-1 1jimid1 ,由选手 j j j 向导师 b e l j bel_j belj 连容量为 1 1 1 的边(如果选手 j j j 出局就不要连),然后对于 i i i ,如果导师 j j j i i i 的前 s i s_i si 档志愿范围内,则由选手 i i i 向导师 j j j 连容量为 1 1 1 的边,尝试找增广路,如果增广成功则说明 i i i 的排名上升 m i d mid mid 时不会沮丧。将 m i d mid mid 向左调整,否则向右调整。

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值