题目
点这里看题目。
分析
不难发现:
[|sum_{i=1}^n(A_{ij}-B_{ij})|=|sum_{i=1}^nA_{ij}-sum_{i=1}^nB_{ij}|
]
行求和同理;于是可以发现 (sum A_{ij}) 都是定值,我们只关心 (B) 的行和和列和。
另外一个,原题直接求值显然过于复杂,因而我们可以进行二分,现在我们要判断二分值 (d) 是否合法。
考虑绝对值的含义, (|a-b|le dLeftrightarrow a-dle ble a+d) 。因此我们可以知道 (B) 的每个行和和列和都有自己的范围,现在要做的就是想办法通过 (B_{ij}) 将两种和联系起来。为了方便,我们称 (B) 的第 (i) 行的和为 (R_i) ,第 (j) 列的和为 (C_j) 。
以下是一种典型的错误想法:对于每个格子建立一个点,并尝试以格子作为左部,行、列作为右部进行建图。
......嗯,这就是错的,因为这会遇到 " 复制 " 流的问题。
不妨这样考虑:我们先给行和确定值,现在只需要将行和拆成 (m) 个元素,并且保证列和合法。
这个思路不难导出如下的做法:
首先建立源汇 (s,t) 。对于每个行建立点,总为左部 (L);对于每个列建立点,总为右部 (R) 。
然后对于 (uin L) ,连接 (s ightarrow u) ,流量区间为 ([R_i-d,R_i+d]) ;对于 (vin R) ,连接 (v ightarrow t) ,流量区间为 ([C_i-d,C_i+d]) 。
如何表示每一个元素?由于每个元素均可以由一行和一列唯一确定,因此一条边 (u ightarrow v(uin L,vin R)) 就确定了一个元素,那么所有跨部的边的流量区间均为题目给定值。
于是,如果这个图有可行流,就说明 (d) 是合法的。
小结:
通常我们会想到将一个格子抽象为点,而这里是将一个格子抽象为边,从而得到了 行-列 的二分图。
代码
#include <cstdio>
typedef long long LL;
#define int LL
const int INF = 1e8;
const int MAXN = 1e5 + 5, MAXM = 1e6 + 5;
const int MAXS = 205;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T MIN( const _T a, const _T b )
{
return a < b ? a : b;
}
template<typename _T>
_T MAX( const _T a, const _T b )
{
return a > b ? a : b;
}
struct Edge
{
int to, nxt, c;
}Graph[MAXM << 1];
int A[MAXN];
int q[MAXN];
int head[MAXN], dep[MAXN], cur[MAXN];
bool vis[MAXN];
int coe[MAXS][MAXS], row[MAXS], col[MAXS];
int N, M, cnt = 1, tot, L, R;
void AddEdge( const int from, const int to, const int C )
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
Graph[cnt].c = C, head[from] = cnt;
}
void AddE( const int from, const int to, const int C ) { AddEdge( from, to, C ), AddEdge( to, from, 0 ); }
void AddELim( const int a, const int b, const int l, const int u ) { AddE( a, b, u - l ), A[a] -= l, A[b] += l; }
bool BFS( const int S, const int T )
{
int h = 1, t = 0, u, v;
for( int i = 1 ; i <= tot ; i ++ ) dep[i] = INF;
dep[q[++ t] = S] = 0;
while( h <= t )
{
u = q[h ++];
for( int i = head[u] ; i ; i = Graph[i].nxt )
if( Graph[i].c && dep[v = Graph[i].to] > dep[u] + 1 )
dep[q[++ t] = v] = dep[u] + 1;
}
return dep[T] < INF;
}
int DFS( const int u, const int lin, const int T )
{
if( u == T ) return lin;
int used = 0, ret, v, c;
for( int &i = cur[u] ; i ; i = Graph[i].nxt )
{
v = Graph[i].to, c = Graph[i].c;
if( dep[v] == dep[u] + 1 && c && ( ret = DFS( v, MIN( lin - used, c ), T ) ) )
{
used += ret, Graph[i].c -= ret, Graph[i ^ 1].c += ret;
if( used == lin ) break;
}
}
if( used < lin ) dep[u] = INF;
return used;
}
int Dinic( const int S, const int T )
{
int ret = 0;
while( BFS( S, T ) )
{
for( int i = 1 ; i <= tot ; i ++ ) cur[i] = head[i];
ret += DFS( S, INF, T );
}
return ret;
}
void Clean()
{
cnt = 1, tot = N * M + N + M;
for( int i = 1 ; i <= tot + 10 ; i ++ )
head[i] = dep[i] = cur[i] = vis[i] = q[i] = A[i] = 0;
}
bool Chk( const int d )
{
Clean();
const int s = ++ tot, t = ++ tot,
S = ++ tot, T = ++ tot;
int pnt = N * M;
for( int i = 1 ; i <= N ; i ++ )
AddELim( s, i + pnt, row[i] - d, row[i] + d );
for( int i = 1 ; i <= M ; i ++ )
AddELim( i + pnt + N, t, col[i] - d, col[i] + d );
for( int i = 1 ; i <= N ; i ++ )
for( int j = 1 ; j <= M ; j ++ )
AddELim( i + pnt, j + pnt + N, L, R );
LL ned = 0;
for( int i = 1 ; i <= tot - 2 ; i ++ )
if( A[i] > 0 ) AddE( S, i, A[i] ), ned += A[i];
else if( A[i] < 0 ) AddE( i, T, - A[i] );
AddE( t, s, INF );
return Dinic( S, T ) == ned;
}
signed main()
{
read( N ), read( M );
for( int i = 1 ; i <= N ; i ++ )
for( int j = 1 ; j <= M ; j ++ )
read( coe[i][j] ), row[i] += coe[i][j], col[j] += coe[i][j];
read( L ), read( R );
int l = 0, r = INF, mid;
while( l < r )
{
mid = l + r >> 1;
if( Chk( mid ) ) r = mid;
else l = mid + 1;
}
write( l ), putchar( '
' );
return 0;
}