1. 程式人生 > >【BZOJ 2400】Spoj 839 Optimal Marks 最小割

【BZOJ 2400】Spoj 839 Optimal Marks 最小割

很有意思的一道題,看到網上有先跑最小割然後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;
}