1. 程式人生 > 其它 >P1791-[國家集訓隊]人員僱傭【最大權閉合圖】

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;
}