bzoj2039 人员雇佣 最小割

       这道题目描述真是醉了。。题意,雇佣每一个经理有一个花费,同时雇佣两个经理可以的到2*E[i,j]的利润,如果一个经理雇佣另一个经理不雇佣会造成E[i,j]的损失(注意是倒扣)。

       这种题目的做法和最大闭全子图差不多是一个道理。。简单说一下吧(其实是怕自己忘记):

       一般来说都是转化成最小割,割完以后和S相连的是要选取的(对应到本题就是要雇佣的),和T相连的是不选取的,然后割表示损失的部分。因此建图如下:

      首先,对于要雇佣的,要和S相连,就要和T割断,每一个点都是如此,因此每一个点(表示每一个经理)向T连一条边,边权为雇佣的费用,这样和T相连的就不需要割断,因此对应不雇佣的;和T割断的就是要雇佣的;

      然后,对于不雇佣的,即要和T相连,和S割断,那么它损失的即为一个经理所能创造的利润E[i,1]+E[i,2]+...+E[i,n],因此将S向每一个点连一条边,容量即刚刚那个式子的结果;

      最后,还要考虑损失的部分,即题中的竞争对手。如果按照上面的方法,i,j都雇佣会造成0的损失(因为和S相连的边不是割),i,j都不雇佣会造成2*E[i,j]的损失(因为和S相连的边都是割),但是一个雇佣一个不雇佣却只会造成E[i,j]的损失,但实际上损失为E[i,j]*3,因此在i,j中连一条容量为(3-1)*E[i,j]=2*E[i,j]的双向边,这样如果i不选(不妨假设),那么久要讲j->i的这一条2*E[i,j]的边也变成割,否则i和S联通,j和T联通就矛盾了,这样就使得i,j中选择一个雇佣的损失变为了E[i,j]就可以了。

       因此,总的建图如下:

1.从S到任意点i连一条容量为Σ(j=1,n)E[i,j]的边;

2.任意两点i,j间连一条容量为2*E[i,j]的双向边;

3.任意点i到T连一条边,容量为雇佣的费用;

AC代码如下(发现当前弧优化好快9000+ms直接变成6000-ms了):

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define N 2005
#define M 4000005
using namespace std;

int n,tot=1,d[N],h[N],fst[N],cur[N],pnt[M],nxt[M]; ll s[N],len[M],inf=(ll)100000000*(ll)100000000;
void add(int aa,int bb,ll cc){
	pnt[++tot]=bb; len[tot]=cc; nxt[tot]=fst[aa]; fst[aa]=tot;
}
bool bfs(int sta,int gol){
	memcpy(cur,fst,sizeof(fst));
	memset(d,-1,sizeof(d)); d[sta]=1;
	int head=0,tail=1; h[1]=sta;
	while (head<tail){
		int x=h[++head],p;
		for (p=fst[x]; p; p=nxt[p]) if (len[p]){
			int y=pnt[p];
			if (d[y]==-1){ d[y]=d[x]+1; h[++tail]=y; }
		}
	}
	return d[gol]!=-1;
}
ll dfs(int x,ll rst){
	if (x==n+1 || !rst) return rst; int p; ll flow=0;
	for (p=cur[x]; p; p=nxt[p]) if (len[p]){
		int y=pnt[p];
		if (d[x]+1==d[y]){
			ll tmp=dfs(y,min(rst,len[p])); if (!tmp) continue;
			flow+=tmp; rst-=tmp;
			len[p]-=tmp; len[p^1]+=tmp; if (!rst) return flow;
		}
	}
	cur[x]=(p)?p:fst[x]; if (!flow) d[x]=-1;
	return flow;
}
int main(){
	scanf("%d",&n); int i,j; ll x,ans=0;
	for (i=1; i<=n; i++){
		scanf("%lld",&x); add(i,n+1,x); add(n+1,i,0);
	}
	for (i=1; i<=n; i++)
		for (j=1; j<=n; j++){
			scanf("%lld",&x);  ans+=x;
			s[i]+=x; if (i<j){ add(i,j,x<<1); add(j,i,x<<1); }
		}
	for (i=1; i<=n; i++){ add(0,i,s[i]); add(i,0,0); }
	while (bfs(0,n+1)) ans-=dfs(0,inf); printf("%lld\n",ans);
	return 0;
}

by lych

2016.1.24

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值