AtCoder Regular Contest 107 F - Sum of Abs(网络流最小割)

题目链接
题意就是给定一个无向图,每个点有权值 a i , b i a_i,b_i ai,bi,现在需要删去其中的一些点,其中删去一个点的花费为 a i a_i ai,删点后的图的分数为每一个联通块的分数之和,一个联通块的分数是这个联通块的点的 b i b_i bi的和的绝对值。要求删点后的图的分数减去删点的花费的最大值( n , m < = 300 n,m<=300 n,m<=300)
很妙的最小割题
对于一个联通块,取绝对值之后就是这个联通块内的所有点的和乘上+1或者-1
这样我们就可以考虑给每一个没有删去的点赋上一个额外的值为+1或-1,其中+1和-1不能相邻因为一个联通块内的所有点的权值都是+1或者-1,然后我们要求其中的最小值。
事实上,我们可以先求所有的 b i b_i bi的绝对值的和 s u m sum sum,这样对于点 i i i,删去它或者给这个点赋上+1或者-1的代价分别是:
如果 b i > 0 b_i>0 bi>0,则删点、赋为+1,-1的代价分别为: a i + b i , 0 , 2 b i a_i+b_i,0,2b_i ai+bi,0,2bi
如果 b i < 0 b_i<0 bi<0,则删点、赋为+1,-1的代价分别为: a i − b i , − 2 b i , 0 a_i-b_i,-2b_i,0 aibi,2bi,0
仔细想想都比较容易得到,这里举 b i < 0 b_i<0 bi<0,赋值为+1的情况为例,原本我们已经求得了所有 b i b_i bi的绝对值的和,相当于对此时的 b i b_i bi取了权值为 − 1 -1 1,而实际上我们应该取+1,所以我们的代价就是原本最初加进去的 ∣ b i ∣ |b_i| bi和要取+1时的 − b i -b_i bi,故为 − 2 b i -2b_i 2bi
转化为这个问题之后其实可以用最小割来解决。我们可以这样连边:先拆点,然后每个点S到i连一条流量为这个点赋值+1的代价的边,i到i’连接一条流量为删去这个点的代价的边,i’到T连接一条流量为这个点赋值-1的代价的边。对于原本在图上的一条边(u,v),我们在u’到v和v’到u分别连接一条流量为inf的边。这样求最小割即可。
大概原理:
在这里插入图片描述
对于u到v这条边中,要么删去其中一个点,要么不能同时选择+1或者-1,对应着在图上割就可以了
代码:

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=610,M=2410;
int n,m,S,T,ans,tot=1,a[N],b[N],cur[N],head[N],to[M],nxt[M],val[M],dep[N];
void add_edge(int u,int v,int w){
	nxt[++tot]=head[u];
	to[tot]=v;
	val[tot]=w;
	head[u]=tot;
	return;
}
void Add_edge(int u,int v,int w){
	add_edge(u,v,w);
	add_edge(v,u,0);
	return;
}
bool bfs(){
	queue<int>q;
	q.push(S);
	for(int i=1;i<=T;i++){
		dep[i]=-1;
		cur[i]=head[i];
	}
	dep[S]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];~i;i=nxt[i]){
			int v=to[i];
			if(!val[i]||~dep[v])
				continue;
			dep[v]=dep[u]+1;
			q.push(v);
		}
	}
	return ~dep[T];
}
int dfs(int u,int now){
	if(u==T||!now)
		return now;
	int ret=0;
	for(int &i=cur[u];~i;i=nxt[i]){
		int v=to[i];
		if(dep[v]!=dep[u]+1||!val[i])
			continue;
		int dist=dfs(v,min(now,val[i]));
		if(dist){
			val[i]-=dist;
			now-=dist;
			val[i^1]+=dist;
			ret+=dist;
		}
	}
	return ret;
}
void Dinic(){
	while(bfs())
		ans-=dfs(S,0x7f7f7f7f);
	return;
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	S=(n<<1|1),T=S+1;
	for(int i=1;i<=n;i++)
		scanf("%d",a+i);
	for(int i=1;i<=n;i++){
		scanf("%d",b+i);
		if(!b[i])
			Add_edge(i,i+n,a[i]);
		if(b[i]<0){
			ans-=b[i];
			Add_edge(S,i,-b[i]*2);
			Add_edge(i,i+n,a[i]-b[i]);
		}
		if(b[i]>0){
			ans+=b[i];
			Add_edge(i+n,T,b[i]*2);
			Add_edge(i,i+n,a[i]+b[i]);
		}
	}
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		Add_edge(v+n,u,2e9);
		Add_edge(u+n,v,2e9);
	}
	Dinic();
	printf("%d\n",ans);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值