JZOJ 2701. 【GDKOI2012模拟02.01】矩阵 (Standard IO) 初涉上下界网络流

题意:
Nope
数据范围:
Nope

Solution

题目要求的是一个最大值最小,那么可以考虑二分答案。

把题目的式子简单变一下:
∣ ∑ A − B ∣ ≤ m i d |\sum A-B|\leq mid ABmid
A − m i d ≤ B ≤ A + m i d A-mid\leq B \leq A+mid AmidBA+mid
也就是说 B B B的每一行每一列的和都要在一个范围之内, B B B的每个数值又要在 L , R L,R L,R之内,于是我们可以算出每一行的上下界,计 s i s_i si表示第 i i i行所有 A A A的和,那么下界为 m a x ( s i − m i d , L ∗ m ) max(s_i-mid,L*m) max(simid,Lm),因为至少有 m m m L L L,上界为 m i n ( s i + m i d , R ∗ m ) min(s_i+mid,R*m) min(si+mid,Rm),因为至多有 m m m R R R,我们将每一行的下界累加,上界累加。每一列也这样做一遍。如果最后行的总上下界与列的总上下界有交集,那么就存在解,第一问就做出来了。

问题在于,如何输出可行方案?

考虑建立一个边有上下界的网络流模型。以往所建立的网络流模型,每条边仅有上界,下界都是0,即可以不流。我们给一条边定义上界 r ( u , v ) r(u,v) r(u,v)和下界 l ( u , v ) l(u,v) l(u,v),其流量 f ( u , v ) f(u,v) f(u,v)必须满足 l ( u , v ) ≤ f ( u , v ) ≤ r ( u , v ) l(u,v)\leq f(u,v) \leq r(u,v) l(u,v)f(u,v)r(u,v)
满足每个点流量守恒的限制且每条边的上下界限制的一个流,被称为无源汇上下界可行流。
如果给定源点和汇点,源点必须只出不进,汇点必须只进不出,别的点要满足流量守恒,这样的模型就叫有源汇上下界可行流。

对于此题,我们可以建立一个有源汇上下界可行流模型:
s 1 i s1_i s1i表示第 i i i行所有 A A A之和,记 s 2 j s2_j s2j表示第 j j j列所有 A A A之和,待检验的答案为 m i d mid mid

建立一个源点 S S S,一个汇点 T T T n n n个点表示 n n n行, m m m个点表示 m m m列, S S S向表示各行的点连边,容量上下界为 [ s 1 i − m i d , s 1 i + m i d ] [s1_i-mid,s1_i+mid] [s1imid,s1i+mid],各行各列各自连边,容量上下界为 [ L , R ] [L,R] [L,R],表示各列的点向 T T T连边,容量上下界为 [ s 2 j − m i d , s 2 j + m i d ] [s2_j-mid,s2_j+mid] [s2jmid,s2j+mid]。此时,我们只需要求该网络的一个可行流。

关于上下界网络流的解法,这位同学的博客里总结的很好,我也不想献丑了,请去看这位同学的总结吧:
Click here
其中这两段说得很好:

理解方法:
对于原图中的每条弧,我们把c−b称为它的自由流量,意思就是只要它流满了下界,这些流多少都没问题。
既然如此,对于每条弧<u,v>,我们强制给v提供b单位的流量,并且强制从u那里拿走b单位的流量,这一步对应着两条附加弧。
如果这一系列强制操作能完成的话,也就是有一组可行流了。

理解方法:
有源汇相比无源汇的不同就在于,源和汇是不满足流量平衡的,那么连接<t,s>之后,源和汇也满足了流量平衡,就可以直接按照无源汇的方式建模

综合一下我自己的理解,上下界网络流其实就是先忽略下界限制,再通过添加附加弧,判断附加弧是否满流,来看有没有满足下界限制的流,并求出这个可行流。

对于此题,我们已经保证存在可行流,就不用判断附加边是否满流了。

Code

#include <cstdio>
#include <cstring>

const int N = 207, K = N * 10, INF = 0x3f3f3f3f;
int max(int a, int b) { return a > b ? a : b; }
int min(int a, int b) { return a < b ? a : b; }

int n, m, L, R, a[N][N], s1[N], s2[N], b[N][N];
int l, r, mid, ans = -1;

int S, T, ss, tt, tot = 1, st[K], to[K * K], nx[K * K], len[K * K], totflow[K];
void add(int u, int v, int w)
{
	to[++tot] = v, nx[tot] = st[u], len[tot] = w, st[u] = tot;
	to[++tot] = u, nx[tot] = st[v], len[tot] = 0, st[v] = tot;
}
int head, tail, que[K], dep[K];
int bfs()
{
	memset(dep, 0, sizeof(dep));
	head = 1, que[tail = 1] = ss, dep[ss] = 1;
	while (head <= tail)
	{
		int u = que[head++];
		for (int i = st[u]; i; i = nx[i])
			if (!dep[to[i]] && len[i] > 0)
			{
				dep[to[i]] = dep[u] + 1, que[++tail] = to[i];
				if (to[i] == tt) return 1;
			}
	}
	return 0;
}
int dinic(int u, int flow)
{
	if (u == tt) return flow;
	int rest = flow, tmp;
	for (int i = st[u]; i; i = nx[i])
		if (dep[to[i]] == dep[u] + 1 && len[i] > 0)
		{
			if (!rest) return flow;
			tmp = dinic(to[i], min(rest, len[i]));
			if (!tmp) dep[to[i]] = 0;
			rest -= tmp, len[i] -= tmp, len[i ^ 1] += tmp;
		}
	return flow - rest;
}

int check()
{
	int l1 = 0, r1 = 0, l2 = 0, r2 = 0;
	for (int i = 1; i <= n; i++)
	{
		if (max(s1[i] - mid, L * m) > min(s1[i] + mid, R * m)) return 0;
		l1 += max(s1[i] - mid, L * m), r1 += min(s1[i] + mid, R * m);
	}
	for (int i = 1; i <= m; i++)
	{
		if (max(s2[i] - mid, L * n) > min(s2[i] + mid, R * n)) return 0;
		l2 += max(s2[i] - mid, L * n), r2 += min(s2[i] + mid, R * n);
	}
	if (l1 > r2 || r1 < l2) return 0;
	return 1;
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]), s1[i] += a[i][j], s2[j] += a[i][j];
	scanf("%d%d", &L, &R);
	l = 0, r = 200000;
	while (l <= r)
	{
		mid = l + r >> 1;
		if (check()) r = mid - 1, ans = mid;
		else l = mid + 1;
	}
	S = n + m + 1, T = S + 1, ss = T + 1, tt = ss + 1; //S,T是原网络源汇点,ss,tt是求可行解时的源汇点
	for (int i = 1; i <= n; i++) add(S, i, 2 * ans), totflow[S] -= max(0, s1[i] - ans), totflow[i] += max(0, s1[i] - ans); //下界小于0视作0
	for (int j = 1; j <= m; j++) add(j + n, T, 2 * ans), totflow[j + n] -= max(0, s2[j] - ans), totflow[T] += max(0, s2[j] - ans);
	for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) add(i, j + n, R - L), totflow[i] -= L, totflow[j + n] += L, b[i][j] = tot; //记录一下反向边的编号,该边在残余网络上的流量就是对应的解
	add(T, S, INF);
	for (int i = 1; i <= n + m; i++)
		if (totflow[i] < 0) add(i, tt, -totflow[i]);
		else add(ss, i, totflow[i]);
	while (bfs()) dinic(ss, INF);
	printf("%d\n", ans);
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++) printf("%d ", len[b[i][j]] + L);
		printf("\n");
	}
	return 0;
}

上下界最大流和最小流先挖个坑。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值