【BZOJ 2400】Spoj 839 Optimal Marks 最小割

236 篇文章 0 订阅
17 篇文章 0 订阅

很有意思的一道题,看到网上有先跑最小割然后dfs的做法,但是还看到一个比较有趣的建图方法。

首先对于题目来说,显然可以因为每一个位之间都不会相互影响所以可以按位处理,枚举每一位然后把每一个点划分为0或者1,这样一来就相当于把每一个元素划分为两个集合,并且要求代价最小,这样可以想到最小割。

然后对于这一道题目要求在保证边权最小的情况下点权和最小,这就相当于要在保证第一条件最优下次要条件也最优,这样我们在网络流中把第一条件的每条边权都扩大10000倍这样次要条件就不会影响到第一条件了(这种思想其实以前遇到过一次)

具体的:

次要条件:s代表0集合,t代表1集合,如果这个点已经确定为1 s->i边权为1 i->t边权为inf这样代表代价必须为1

如果这个点确定为0  s->i边权为inf 不会被割掉

没有确定 s-> 边权为1 可割可不割

第一条件:每一条边对应原图中的边相连边权扩大10000倍

#include<cstdio>
#include<cstring>
#include<iostream>
#define maxn 100021
#define inf 100000000000000ll
#define LL long long
using namespace std;
void read(LL& x){
	x=0;char c=getchar();LL f=1;
	for(;c>'9'||c<'0';c=getchar())if(c=='-')f=-1;
	for(;c>='0'&&c<='9';c=getchar())x=x*10+c-'0';
	x*=f;
}
LL n,m,tot=1,head[maxn],last[maxn],q[maxn];
LL h[maxn],s,t,w[maxn],bin[55],ans1,ans2;
struct data{LL a,b;}ed[maxn];
struct edge{LL v,next,w;}e[maxn];
void adde(LL a,LL b,LL c){
	e[++tot].v=b;e[tot].next=head[a],e[tot].w=c;
	head[a]=tot;
	e[++tot].v=a;e[tot].next=head[b],e[tot].w=0;
	head[b]=tot;
}
bool bfs(){
	for(LL i=0;i<=t;i++)h[i]=-1;
	LL l=0,r=1;
	q[l]=s,h[s]=0;
	while(l<r){
		LL u=q[l++];
		for(LL v,i=head[u];i;i=e[i].next){
			if(h[v=e[i].v]==-1&&e[i].w){
				h[v]=h[u]+1;
				q[r++]=v;
			}
		}
	}return h[t]!=-1;
}
LL dfs(LL u,LL f){
	if(!f||u==t)return f;
	LL used=0,w;
	for(LL v,i=last[u];i;i=e[i].next){
		if(h[v=e[i].v]==h[u]+1&&e[i].w){
			last[u]=i;
			w=min(f-used,e[i].w);
			w=dfs(v,w);
			used+=w,e[i].w-=w,e[i^1].w+=w;
			if(used==f)return f;
		}
	}
	if(!used)h[u]=-1;
	return used;
}
LL dinic(){
	LL ans=0;
	while(bfs()){
		for(LL i=s;i<=t;i++)last[i]=head[i];
		ans+=dfs(s,inf);
	}return ans;
}
void solve(LL x){
	for(LL i=s;i<=t;i++)head[i]=0;
	tot=1;
	for(LL i=1;i<=n;i++){
		if(w[i]<0){
			adde(s,i,1);//这一位可以为0或者1代表这条边可割可不割所以与s连边割掉代表选1 
		}else{
			if(w[i]&x)adde(i,t,inf),adde(s,i,1);//必须选一割掉边代价为1
			else adde(s,i,inf); 
		}
	}
	for(LL i=1;i<=m;i++){
		adde(ed[i].a,ed[i].b,10000);
		adde(ed[i].b,ed[i].a,10000);
	}
	LL tmp=dinic();
	ans1+=tmp/10000*x;
	ans2+=tmp%10000*x;
}
int main(){	
	read(n),read(m);s=0,t=n+1;
	for(LL i=1;i<=n;i++)read(w[i]);
	for(LL i=1;i<=m;i++)read(ed[i].a),read(ed[i].b);
	bin[0]=1;for(LL i=1;i<=30;i++)bin[i]=bin[i-1]<<1;
	for(LL i=30;i>=0;i--)
		solve(bin[i]);
	printf("%lld\n%lld",ans1,ans2);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值