BZOJ3997: [TJOI2015]组合数学(网络流)
3997: [TJOI2015]组合数学
Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 405 Solved: 284
[Submit][Status][Discuss]
Description
给出一个网格图,其中某些格子有财宝,每次从左上角出发,只能向下或右走。问至少走多少次才能将财宝捡完。此对此问题变形,假设每个格子中有好多财宝,而每一次经过一个格子至多只能捡走一块财宝,至少走多少次才能把财宝全部捡完。
Input
第一行为正整数T,代表数据组数。
Output
输出一个整数,表示至少要走多少次。
Sample Input
3 3
0 1 5
5 0 0
1 0 0
Sample Output
HINT
N<=1000,M<=1000.每个格子中财宝数不超过10^6
Source
把公式写出来就是∑ni=1∑nj=1Bij×Ai×Aj–∑ni=1Ai×Ci
于是可以看出是一个最大权闭合图,我要获得bij这个点的收益就要付出ci和cj的费用。
经典的最小割。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define clr(x,y) memset(x,y,sizeof(x));
#define maxn 505
#define maxm 505500
#define inf int(1e9)
using namespace std;
struct data{int obj;int pre;int c;
}e[3005000];
int head[maxm],b[maxn][maxn],c[maxn],uu[maxm],s,t,n,sum,tot=1,q[3005000];
void insert(int x,int y,int z){
e[++tot].obj=y; e[tot].c=z; e[tot].pre=head[x]; head[x]=tot;
e[++tot].obj=x; e[tot].c=0; e[tot].pre=head[y]; head[y]=tot;
}
bool bfs(){
clr(uu,-1);
int l=0,r=1; q[1]=s; uu[s]=0;
while (l<r){
int u=q[++l];
for(int j=head[u];j;j=e[j].pre){
int v=e[j].obj;
if (uu[v]==-1&&e[j].c>0) {
q[++r]=v; uu[v]=uu[u]+1;
}
}
}
if (uu[t]<0) return 0;
return 1;
}
int dfs(int u,int mx){
int used=0,w;
if (u==t||mx==0) return mx;
for(int j=head[u];j;j=e[j].pre){
int v=e[j].obj;
if (uu[v]==uu[u]+1&&e[j].c>0){
w=dfs(v,min(mx-used,e[j].c));
if (w<=0) {uu[v]=-1; continue;}
e[j].c-=w; e[j^1].c+=w; used+=w;
if (used==mx) return used;
}
}
return used;
}
int dinic(){
int ans=0;
while (bfs())
ans+=dfs(s,inf);
return ans;
}
int main(){
scanf("%d",&n);
s=0; t=n+n*n+1;
rep(i,1,n)
rep(j,1,n)
scanf("%d",&b[i][j]),sum+=b[i][j];
rep(i,1,n) scanf("%d",&c[i]),insert(n*n+i,t,c[i]);
rep(i,1,n){
rep(j,1,n){
insert(s,(i-1)*n+j,b[i][j]);
insert((i-1)*n+j,n*n+i,inf);
insert((i-1)*n+j,n*n+j,inf);
}
}
printf("%d\n",sum-dinic());
return 0;
}