=== ===
这里放传送门
=== ===
题解
这种式子看一眼就头大。。。日= =
可以发现它实际上就是在说要让每一行和每一列的对应元素差值之和的最大值最小。那么可以考虑二分这个最大值然后判定可行性。
如果二分了一个最大值
lim
,这里以某一行为例,式子可以转化成
|∑jAij−∑jBij|≤lim
。那么把绝对值符号展开就成了
−lim≤∑jAij−∑jBij≤lim
。这个式子里
Aij
都是已知项,那么移项一下就可以变成
−lim−∑jAij≤−∑jBij≤lim−∑jAij
,也就是
∑jAij−lim≤∑jBij≤lim+∑jAij
。
这个式子又代表了什么呢?可以发现它的意思就是
B
矩阵每一行和每一列的数字和要在某一段范围之内。然后题目中原本还有一个限制就是
对于这个题因为有一段范围的限制所以要用到上下界网络流。每一行和每一列分别弄一排点,从S向每一行的点连边,每一列的点向T连边,设置上下界为它这一行或者这一列的限制。每一行向每一列所有点连边,上下界为题目中给定的单点限制 [L,R] 。用有上下界有源汇的网络流模型进行判定,如果能跑出可行流就说明有解。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inc(x)(x=(x==500)?1:x+1)
#define inf 2000000000
using namespace std;
const int base=1000;
int n,m,a[210][210],p[510],gap[510],d[510],pre[510],cur[510],tot,sum,f[510];
int rs[210],cs[210],S,T,SS,TT,L,R;
struct edge{
int to,flw,nxt;
}e[200010];
void add(int from,int to,int flow){
e[tot].to=to;e[tot].flw=flow;
e[tot].nxt=p[from];p[from]=tot++;
}
void Bfs(){
int q[510],head,tail;
for (int i=S;i<=T;i++){
cur[i]=p[i];d[i]=T+1;
}
head=0;tail=1;q[tail]=T;d[T]=0;
while (head!=tail){
int u;inc(head);u=q[head];
for (int i=p[u];i!=-1;i=e[i].nxt)
if (e[i^1].flw>0&&d[e[i].to]==T+1){
int v=e[i].to;d[v]=d[u]+1;
inc(tail);q[tail]=v;
}
}
}
int Increase(){
int Min=inf;
for (int i=T;i!=S;i=e[pre[i]^1].to)
Min=min(Min,e[pre[i]].flw);
for (int i=T;i!=S;i=e[pre[i]^1].to){
e[pre[i]].flw-=Min;
e[pre[i]^1].flw+=Min;
}
return Min;
}
int ISAP(){
int u=S,Flow=0;
bool flag=false;
Bfs();
for (int i=S;i<=T;i++) gap[d[i]]++;
while (d[S]<=T){
if (u==T){
Flow+=Increase();
u=S;
}
flag=false;
for (int i=p[u];i!=-1;i=e[i].nxt){
int v=e[i].to;cur[u]=p[i];
if (e[i].flw>0&&d[u]==d[v]+1){
pre[v]=i;u=v;flag=true;break;
}
}
if (flag==false){
int v=T;
for (int i=p[u];i!=-1;i=e[i].nxt)
if (e[i].flw>0) v=min(v,d[e[i].to]);
gap[d[u]]--;cur[u]=p[u];
if (gap[d[u]]==0) break;
d[u]=v+1;++gap[d[u]];
if (u!=S) u=e[pre[u]^1].to;
}
}
return Flow;
}
bool check(int lim){
memset(p,-1,sizeof(p));
tot=sum=0;
memset(f,0,sizeof(f));
memset(gap,0,sizeof(gap));
for (int i=1;i<=n;i++){
int low=rs[i]-lim,up=rs[i]+lim;//计算上界和下界
low=max(low,0);//如果流量出现负数应该忽略,因为下界至少是0
f[i]+=low;f[SS]-=low;
add(SS,i,up-low);add(i,SS,0);
}
for (int i=1;i<=m;i++){
int low=cs[i]-lim,up=cs[i]+lim;
low=max(low,0);
f[TT]+=low;f[i+n]-=low;
add(i+n,TT,up-low);add(TT,i+n,0);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
f[j+n]+=L;f[i]-=L;
add(i,j+n,R-L);add(j+n,i,0);
}
add(TT,SS,inf);add(SS,TT,0);
for (int i=S+1;i<T;i++)
if (f[i]>0){
add(S,i,f[i]);add(i,S,0);sum+=f[i];
}else{add(i,T,-f[i]);add(T,i,0);}
if (ISAP()!=sum) return false;
return true;
}
int divide(int l,int r){
int mid;
while (l!=r){
mid=(l+r)>>1;
if (check(mid)) r=mid;
else l=mid+1;
}
return l;
}
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]);
rs[i]+=a[i][j];
cs[j]+=a[i][j];
}
SS=n+m+1;TT=SS+1;
S=0;T=TT+1;
scanf("%d%d",&L,&R);
printf("%d\n",divide(0,250000));
return 0;
}