题意: 给一个矩阵,给出约束:i(0<i<n)行至少去ai个数,j行至少取bi个数,要求取的数值之和最小。
开始一见,就直接建了二分图,但是,发现这是有下界无上界最小费用流问题,肿么办。。。问题转化:所谓正难则反!现在某行/列要至少取k个,总和最小,不就是那行/列最多留下K个,使留下的和最大?其实也就是最多取k个,使值最大,转化为下界为0,有上界的最大费用问题(普通问题)。“取”,“不取”,本质都是一样的,正是“无为”的思想!取,则最小;不取,最大。道也。道之道非常道,名可名非常名~~
还有一点,转化之后,最大费用时未必最大流。解决方法有二:
其一:每次增广后,加判断,若费用开始递减,则跳出,此时取最大。(据说二分图费用是先增后减函数:每次增广,当费用最大的时候,但是这时候流量不是最大,所以减小费用来增大流量,不知道一般图是不是。。。)
其二:释放法,X部所有点直接向汇点连边,费用0,流量Inf,我感觉这样,当X部还有流量的时候,直接就向汇点释放了,所有必是最大费用(不会再减少了)。
俩种方法我都试过,AC。
#include<cstdio> #include<iostream> #include<queue> #include<cstring> #include<string> using namespace std; const int maxv=200; const int maxe=200*200*2+800; const int inf=0x3f3f3f3f; int nume=0;int e[maxe][4];int head[maxv]; int n,m;int ss,tt; int val[105][105]; void inline adde(int i,int j,int c,int w) { e[nume][0]=j;e[nume][1]=head[i];head[i]=nume; e[nume][2]=c;e[nume++][3]=w; e[nume][0]=i;e[nume][1]=head[j];head[j]=nume; e[nume][2]=0;e[nume++][3]=-w; } int inq[maxv];int pre[maxv];int prv[maxv]; int d[maxv]; bool spfa(int &sum,int &flow) { for(int i=0;i<=tt;i++) { inq[i]=0; d[i]=inf; } queue<int>q; q.push(ss); inq[ss]=1; d[ss]=0; while(!q.empty()) { int cur=q.front(); q.pop(); inq[cur]=0; for(int i=head[cur];i!=-1;i=e[i][1]) { int v=e[i][0]; if(e[i][2]>0&&d[cur]+e[i][3]<d[v]) { d[v]=d[cur]+e[i][3]; pre[v]=i; prv[v]=cur; if(!inq[v]) { q.push(v); inq[v]=1; } } } } if(d[tt]==inf)return 0; int cur=tt; int minf=inf; while(cur!=ss) { int fe=pre[cur]; minf=e[fe][2]<minf?e[fe][2]:minf; cur=prv[cur]; } cur=tt; while(cur!=ss) { e[pre[cur]][2]-=minf; e[pre[cur]^1][2]+=minf; cur=prv[cur]; } flow+=minf; sum+=d[tt]*minf; return 1; } int mincost(int &flow) { int sum=0; // int lastsum=0; while(spfa(sum,flow)) { ; // if(-lastsum>-sum)return lastsum; //取最值法 // lastsum=sum; // cout<<sum<<endl; } return sum; } int sum_all=0; void init() { nume=0; sum_all=0; ss=n+m; tt=n+m+1; for(int i=0;i<=tt;i++) head[i]=-1; } void read_build() { for(int i=0;i<n;i++) for(int j=0;j<m;j++) { scanf("%d",&val[i][j]); sum_all+=val[i][j]; adde(i,j+n,1,-val[i][j]); } int aa; for(int i=0;i<n;i++) { scanf("%d",&aa); adde(ss,i,m-aa,0); adde(i,tt,m-aa,0); //X部直接向汇点连边(容量够释放就行) } for(int i=0;i<m;i++) { scanf("%d",&aa); adde(i+n,tt,n-aa,0); } /* for(int i=0;i<=m+n+1;i++) for(int j=head[i];j!=-1;j=e[j][1]) { printf("%d->%d:f %dw %d\n",i,e[j][0],e[j][2],e[j][3]); }*/ } int main() { int T; cin>>T; while(T--) { scanf("%d%d",&n,&m); init(); read_build(); int flow=0; int ans=sum_all+mincost(flow); printf("%d\n",ans); } return 0; }
用“道”的思想解决费用流问题---取/不取皆是取 (有下界->有上界) / ACdreamoj 1171,布布扣,bubuko.com
时间: 2024-10-15 01:20:32