P1791-[国家集训队]人员雇佣【最大权闭合图】
正题
题目链接:https://www.luogu.com.cn/problem/P1791
题目大意
有\(n\)个人,雇佣第\(i\)个需要\(A_i\)的费用,对于\(E_{i,j}\)表示如果\(i\)选了的话,选择\(j\)会获得\(E_{i,j}\)的费用,不选\(j\)会花费\(E_{i,j}\)的费用。
\(1\leq n\leq 1000\)
解题思路
考虑网最大权值闭合图,先加上所有可以获得的权值,然后考虑需要失去的最小权值。
因为每个人可以选或者不选,那么就可以让\(s\)连接\(i\)且\(i\)连接\(t\)这样两边必须割掉一条表示选择或者不选择。
考虑让\(s->i\)表示选择,那么\(s->i\)权值为\(A_i\)。
\(i->t\)表示不选择那么所有由\(i\)产生的费用都不能获得,权值为\(\sum_{j=1}^mE_{i,j}\)。
然后对于一个\(E_{i,j}\)如果\(i\)选择了且\(j\)没有选择那么就会失去\(2\times E_{i,j}\)的流量,在\(i\)和\(j\)之间连一条\(2\times E_{i,j}\)的就好了。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
const ll N=2e4+10,inf=2147483647;
struct node{
ll to,next,w;
}a[N*4];
ll n,s,t,tot,cnt,A[N],ls[N],dep[N],ans;
queue<int> q;
void addl(ll x,ll y,ll w){
a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;a[tot].w=w;
a[++tot].to=x;a[tot].next=ls[y];ls[y]=tot;a[tot].w=0;
return;
}
bool bfs(){
memset(dep,0,sizeof(dep));dep[s]=1;
while(!q.empty())q.pop();q.push(s);
while(!q.empty()){
ll x=q.front();q.pop();
for(ll i=ls[x];i;i=a[i].next){
ll y=a[i].to;
if(dep[y]||!a[i].w)continue;
dep[y]=dep[x]+1;
if(y==t)return 1;
q.push(y);
}
}
return 0;
}
ll dinic(ll x,ll flow){
if(x==t)return flow;
ll rest=0,k;
for(ll i=ls[x];i;i=a[i].next){
ll y=a[i].to;
if(dep[y]!=dep[x]+1||!a[i].w)continue;
rest+=(k=dinic(y,min(a[i].w,flow-rest)));
a[i].w-=k;a[i^1].w+=k;
if(rest==flow)return flow;
}
if(!rest)dep[x]=0;
return rest;
}
signed main()
{
scanf("%lld",&n);
s=n+1;t=s+1;tot=1;
for(ll i=1;i<=n;i++){
ll x;
scanf("%lld",&x);
addl(s,i,x);
}
for(ll i=1;i<=n;i++){
ll S=0;
for(ll j=1;j<=n;j++){
ll x;scanf("%lld",&x);
if(!x)continue;
S+=x;addl(i,j,2*x);
}
addl(i,t,S);ans+=S;
}
while(bfs())
ans-=dinic(s,inf);
printf("%lld\n",ans);
return 0;
}