POJ2396 Budget【矩阵行列和限制的上下界网络流】

题目描述:

给出n*m的矩阵中每一行和每一列的和,以及每个元素的范围。求一个合法矩阵。
n<=200,m<=20.

题目分析:

把输入看错的我自闭了一个下午QWQ。。注意0 y = 3表示的是第y列都是3而不是和为3。。

给出了每个元素的范围容易想到上下界网络流。

一开始我想把行列和矩阵点全部建出来,然后行列向矩阵点连边,发现不能保证和的限制。于是想到行用矩阵点的入边,列用出边,然后新建一个点向行点连,列点向新建的那个点连,然后发现矩阵点完全可以省去,直接变成一条边。

于是模型就变成了:
新建一个点S。
S向每个行点连流量下界为行和的边。
行点向每个列点连以对应的矩阵点的范围为上下界的边。
列点向S连流量下界为列和的边,形成一个循环流。

于是求一个无源汇上下界可行流即可,行点和列点之间的流量就是对应的矩阵点的值。

这道题启示我们矩阵中的点可以用对应行与列的连边来表示

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 235
#define maxm 10005
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m,S,T,ss,tt;
int fir[maxn],cur[maxn],nxt[maxm],to[maxm],c[maxm],tot=1;
inline void line(int x,int y,int z,int rz=0){
	nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,c[tot]=z;
	nxt[++tot]=fir[y],fir[y]=tot,to[tot]=x,c[tot]=rz;
}
namespace Maxflow{
	int d[maxn],vd[maxn],sz;
	int aug(int u,int augco){
		if(u==T) return augco;
		int need=augco,delta;
		for(int &i=cur[u];i;i=nxt[i]) if(c[i]&&d[u]==d[to[i]]+1){
			delta=aug(to[i],min(need,c[i]));
			c[i]-=delta,c[i^1]+=delta;
			if(!(need-=delta)||d[S]==sz) return augco-need;
		}
		if(!--vd[d[u]]) d[S]=sz;
		else vd[++d[u]]++,cur[u]=fir[u];
		return augco-need;
	}
	int SAP(){
		sz=T,memset(d,0,(sz+1)<<2),memset(vd,0,(sz+1)<<2),memset(cur,0,(sz+1)<<2);
		int flow=0;
		while(d[S]<sz) flow+=aug(S,inf);
		return flow;
	}
}
#define ID(i,j) ((i-1)*m+(j))
int L[maxn*maxn],R[maxn*maxn],tf[maxn],ans[maxn][maxn];
inline void upd(int x,int y,int op,int z){
	int t=ID(x,y);
	if(op!='<') L[t]=max(L[t],op=='='?z:z+1);
	if(op!='>') R[t]=min(R[t],op=='='?z:z-1);
}
int main()
{
	int Test,k,x,y,z;char op;
	scanf("%d",&Test);
	while(Test--){
		scanf("%d%d",&n,&m),ss=n+m+1,tt=n+m+2;
		memset(fir,0,(tt+1)<<2),tot=1,memset(tf,0,(tt+1)<<2);
		for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) L[ID(i,j)]=0,R[ID(i,j)]=inf;
		for(int i=1;i<=n;i++) scanf("%d",&x),tf[i]+=x,tf[0]-=x;
		for(int i=1;i<=m;i++) scanf("%d",&x),tf[n+i]-=x,tf[0]+=x;
		scanf("%d",&k);
		while(k--){
			scanf("%d%d %c %d",&x,&y,&op,&z);
			if(!x&&!y) {for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) upd(i,j,op,z);}
			else if(!x) for(int i=1;i<=n;i++) upd(i,y,op,z);
			else if(!y) for(int j=1;j<=m;j++) upd(x,j,op,z);
			else upd(x,y,op,z);
		}
		bool flg=1;
		for(int i=1;i<=n&&flg;i++)
			for(int j=1;j<=m;j++){
				int id=ID(i,j);
				if(L[id]>R[id]) {flg=0;break;}
				tf[i]-=L[id],tf[n+j]+=L[id],ans[i][j]=L[id];
				if(L[id]<R[id]) line(i,n+j,R[id]-L[id]);
			}
		if(!flg) {puts("IMPOSSIBLE\n");continue;}
		int sum=0;
		for(int i=0;i<=n+m;i++)
			if(tf[i]>0) line(ss,i,tf[i]),sum+=tf[i];
			else if(tf[i]<0) line(i,tt,-tf[i]);
		S=ss,T=tt;
		if(sum!=Maxflow::SAP()) {puts("IMPOSSIBLE\n");continue;}
		for(int i=1;i<=n;i++) for(int j=fir[i];j;j=nxt[j]) if(to[j]>n) ans[i][to[j]-n]+=c[j^1];
		for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) printf("%d%c",ans[i][j],j==m?10:32);
		putchar('\n');
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值