[BZOJ2406]矩阵

矩阵

题解

首先我们看到题面中那个奇怪的式子,要让这个值最小,这确实很难有什么思路。
但要让最大值最小还是很容易联想到二分的。
我们可以先二分这个最大值,判断当前二分值是否合法,即我们能否构造出来一个方案,其最大值小于我们的二分值。
我们再将原式子转化一下, ∑ i = 1 n ( A i , j − B i , j ) = ( ∑ i = 1 n A i , j ) − ( ∑ i = 1 n B i , j ) \sum_{i=1}^{n}(A_{i,j}-B_{i,j})=(\sum_{i=1}^{n}A_{i,j})-(\sum_{i=1}^{n}B_{i,j}) i=1n(Ai,jBi,j)=(i=1nAi,j)(i=1nBi,j),也就是我们没必要记录单点的值,只需要让和的差在范围内。
所以我们可以的到 ( ∑ i = 1 n B i , j ) ∈ [ ∑ i = 1 n A i , j − m i d , ∑ i = 1 n A i , j + m i d ] (\sum_{i=1}^{n}B_{i,j})\in [\sum_{i=1}^{n}A_{i,j}-mid,\sum_{i=1}^{n}A_{i,j}+mid] (i=1nBi,j)[i=1nAi,jmid,i=1nAi,j+mid]的范围限制。
显然,相当于我们对于所有的行与所有的列的和都有了一个范围的限制。
如果这范围限制只有单独的一个值话,我们是很容易想到通过网络流来求解的。
但我们仔细想想,我们网络流的求解固定的值其实是使得我们每行每列的和都小于某个值,在这种情况下能否全部取等。
如果枚举每行每列的取值,看是否有一种情况合法的话显然是会T掉的,不如考虑上下节网络流,给每个和的取值加一个下界,在有下界的情况下能否跑出可行流。
显然,我们可以把行与列看成一个二部图,行与列之间连接流量为 [ L , R ] [L,R] [L,R]的边,代表该行该列位置选择点的值。
源点汇点与行列间的连边就是该行或该列总和的范围限制。
最后跑一下有没有解就可以得知该二分值可不可行了。

时间复杂度 O ( ( n + m ) 2 n m log ⁡   n ) O\left((n+m)^2nm\log\,n\right) O((n+m)2nmlogn)

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 505
#define MAXM 100005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;       
const int INF=0x3f3f3f3f;       
const int mo=993244853;
const int inv2=499122177;
const double jzm=0.997;
const int zero=10000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<LL,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,m,head[MAXN],tot,cnt,S,T,dis[MAXN],cur[MAXN];
int val[MAXN],summ,L,R,a[205][205],sumr[MAXN],sumc[MAXN];
bool vis[MAXN];queue<int>q;
struct edge{int to,nxt,flow,op;}e[MAXM];
void addEdge(int u,int v,int f){e[++tot]=(edge){v,head[u],f};head[u]=tot;}
void addedge(int u,int v,int f){
	addEdge(u,v,f);e[tot].op=tot+1;
	addEdge(v,u,0);e[tot].op=tot-1;
}
bool bfs(){
	for(int i=1;i<=cnt;i++)dis[i]=vis[i]=0,cur[i]=head[i];
	while(!q.empty())q.pop();q.push(S);vis[S]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;if(vis[v]||!e[i].flow)continue;
			vis[v]=1;dis[v]=dis[u]+1;q.push(v);
			if(v==T)return 1;
		}
	}
	return 0;
}
int dosaka(int u,int maxf){
	if(u==T)return maxf;int res=0;
	for(int i=cur[u];i;cur[u]=i=e[i].nxt){
		int v=e[i].to;if(!e[i].flow||dis[v]!=dis[u]+1)continue;
		int tmp=dosaka(v,min(maxf-res,e[i].flow));
		res+=tmp;e[i].flow-=tmp;e[e[i].op].flow+=tmp;
		if(res==maxf)return res;
	}
	return res;
}
int sakura(){int res=0;while(bfs())res+=dosaka(S,INF);return res;}
bool check(int mid){
	cnt=n+m;int s=++cnt,t=++cnt,vals=0,valt=0;S=++cnt;T=++cnt;summ=0;
	for(int i=1;i<=n;i++){
		int up=sumr[i]+mid,dn=max(0,sumr[i]-mid),dif=up-dn;
		addedge(s,i,dif);val[s]+=dn;val[i]-=dn;
	}
	for(int i=1;i<=m;i++){
		int up=sumc[i]+mid,dn=max(0,sumc[i]-mid),dif=up-dn;
		addedge(i+n,t,dif);val[t]-=dn;val[i+n]+=dn;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			addedge(i,j+n,R-L),val[i]+=L,val[j+n]-=L;
	for(int i=1;i<=n+m+2;i++){
		if(val[i]>0)addedge(i,T,val[i]),summ+=val[i];
		if(val[i]<0)addedge(S,i,-val[i]);
	}
	addedge(t,s,INF);int res=sakura();
	for(int i=1;i<=cnt;i++)head[i]=val[i]=0;tot=0;
	return res==summ;
}
signed main(){
	read(n);read(m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)read(a[i][j]),
			sumr[i]+=a[i][j],sumc[j]+=a[i][j];
	read(L);read(R);
	int l=0,r=1000000;
	while(l<r){int mid=l+r>>1;if(check(mid))r=mid;else l=mid+1;}
	printf("%d\n",l);
	return 0;
}

谢谢!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值