SPOJ 839 Optimal Marks(经典二进制最小割)

洛谷传送门

胡伯涛的论文里的例题。

给定一个无向图,有些点有编号,没有编号的点需要你指定

最后这张图的权值是 ∑ ( u , v ) ∈ E m a r k u ⊕ m a r k v \sum\limits_{(u,v)\in E}mark_u\oplus mark_v (u,v)Emarkumarkv

最小化这个权值,输出你构造的编号

思路非常巧妙

因为异或运算非常难顶,但是异或的话,每一位二进制是独立的

所以可以把二进制拆分,现在只考虑最小化第 i i i位的二进制贡献

问题变成了每个点是0或者1,有些点指定了,有些点需要你来加

那么如果一条边的两个点数字相同不需要计算贡献,不相同计算贡献

想到了什么??最小割!!

S S S点集看作数字 1 1 1, T T T点集看作数字 0 0 0

那么 S S S向已编号的数字为 1 1 1的点连流量为 i n f inf inf得边

已编号的数字为 0 0 0的点向 T T T连流量 i n f inf inf的边

对于原图的边 ( u , v ) (u,v) (u,v),彼此间连流量 1 1 1的边即可

这样就分成了两个点集

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+10; 
const int inf=1e12;
struct edge{
	int to,nxt,flow;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int flow)
{
	d[++cnt] = (edge){v,head[u],flow},head[u]=cnt;
	d[++cnt] = (edge){u,head[v],0},head[v]=cnt;
}
int id[maxn],p[maxn],l[maxn],r[maxn],ans[maxn];
int n,m,k,s,t,dis[maxn];
bool bfs()
{
	queue<int>q;
	for(int i=0;i<=t;i++)	dis[i]=0;
	q.push(s); dis[s]=1;
	while( !q.empty() )
	{
		int u=q.front(); q.pop();
		for(int i=head[u];i;i=d[i].nxt )
		{
			int v=d[i].to;
			if( d[i].flow&&dis[v]==0 )
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[t]!=0;
}
int dinic(int u,int flow)
{
	if( u==t )	return flow;
	int res=flow;
	for(int i=head[u];i&&res;i=d[i].nxt )
	{
		int v=d[i].to;
		if( dis[v]==dis[u]+1&&d[i].flow )
		{
			int temp = dinic(v,min(d[i].flow,res ));
			res-=temp;
			d[i].flow-=temp,d[i^1].flow+=temp;
			if( temp==0 )	dis[v]=0;	
		} 
	}
	return flow-res;
}
signed  main()
{
	int T; cin >> T;
	while( T-- )
	{
		cin >> n >> m;
		for(int i=1;i<=m;i++)	scanf("%lld%lld",&l[i],&r[i]);
		int k; cin >> k;
		for(int i=1;i<=k;i++)
			scanf("%lld%lld",&id[i],&p[i]);
		for(int i=0;i<=30;i++)//枚举每一位二进制 
		{
			s=0,t=n+1,cnt=1;
			for(int j=0;j<=t;j++)	head[j]=0;
			for(int j=1;j<=m;j++)
			{
				add(l[j],r[j],1);
				add(r[j],l[j],1);
			}
			for(int j=1;j<=k;j++)
				if( p[j]&(1<<i) )	add(s,id[j],inf);
				else	add(id[j],t,inf);
			while( bfs() )	dinic(s,inf);
			for(int j=1;j<=n;j++)
				if( dis[j] )	ans[j]+=(1<<i);
		}
		for(int i=1;i<=n;i++)	printf("%lld\n",ans[i]);
		for(int i=0;i<=n;i++)	ans[i]=0;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值