网络流/最小割点集 [USACO5.4]奶牛的电信

最小割点集

大意:
给定一张图,求其最小割点集的大小

思路:

蒟蒻如我,看到题目直接嗷嗷大叫:“卧槽是裸的最小割边!这题我会做!”

兴冲冲敲完dinic莽一发

80分。。。

做题从不带眼睛。。。(思路都错了居然也还有80分。。。)

那这个题是要求最小割点,所以我们不妨想想看怎么把它转化为普通的最大流问题。

一个很精妙的思路是拆点。

把一个点A拆成A和A',两者有一条单向边AA',权值为1,为了构造网络流我们就会让其反向边权值为0.那么当我们要拆点是,实质上就是把这条权值为1的边去了,也就是流量减了1.现在要求点的最小割,自然也就是求边的最小割了,也就是最大流。那么这个多出来的点不妨就设为i+n。

for(int i=1;i<=n;++i)
	{
		add(i,i+n,1);
		add(i+n,i,0);//拆点小trick 
	}

那么模型转化完了,还有一个要考虑的是其余的普通边的问题,它们要怎么连?

需要注意的是,我们求的最小割,是针对这些把点分开的边,而不是普通的边,所以这些普通双向边的加入不应对我们的流量造成影响,换句话说,我们应该把它们的流量设为inf。而且它们跟那些边权为1的边应该是串联的关系,也就是y应该跟x+n连,x应该跟y+n连,不然如果是直接x跟y连,两个点的联通就可以做到跟1没关系了,那求出来的最大流自然也会变成inf。可以看图理解一下。

(反向边我就不画了)

代码就应该是

for(int i=1;i<=m;++i)
	{
		a=read();b=read();
		add(a+n,b,inf);
		add(b,a+n,0);
		add(b+n,a,inf);
		add(a,b+n,0);
	}

这些准备工作都做完后,那就是快乐的板子时间了!

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=10100;
#define endl '\n'
const ll inf=1<<30;
struct ty{
	ll t,l,next;
}edge[N<<2];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
void add(ll a,ll b,ll c)
{
	edge[++cnt].t=b;
	edge[cnt].l=c;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll cur[N];//记录弧 
ll vis;//这次是否能到达终点
ll ans=0;

ll n,m,s,t,x;
ll a,b,c;  
 
bool bfs()//用于分层
{
//    memset(inque,0,sizeof inque);
//    memset(dep,0x3f,sizeof dep);
    for(int i=0;i<=n*2;++i)
    {
    	cur[i]=head[i];
    	dep[i]=0x3f3f3f3f3f3f3f3f;
    	inque[i]=0;
	}
    dep[s]=0;//起点
	queue<ll> q;
	q.push(s);
	while(!q.empty())
	{
		ll ty=q.front();
		q.pop();
		inque[ty]=0;//
		for(int i=head[ty];i!=-1;i=edge[i].next)
		{
			ll y=edge[i].t;
			if(dep[y]<=dep[ty]+1) continue;//无需更新
			if(edge[i].l==0) continue;
			dep[y]=dep[ty]+1;
			if(inque[y]) continue;//看是否在队里 
			inque[y]++;
			q.push(y); 
		} 
	}
	if(dep[t]!=0x3f3f3f3f3f3f3f3f) return 1;
	return 0;	
} 
 
ll dfs(ll u,ll flow)//节点,当前最小流量 
{
	ll rl=0;
	if(u==t)
	{
		vis=1;
		ans+=flow;
		return flow;
	} 
	ll used=0;//该点用过的流量 
	for(int i=cur[u]/*是cur!这里不是head(当前弧优化)*/;i!=-1;i=edge[i].next)
	{
		cur[u]=i;//修改当前弧 
		ll y=edge[i].t;
		if(dep[y]==dep[u]+1&&edge[i].l)
		{
			rl=dfs(y,min(flow-used,edge[i].l));//减去后的费用拿去比较 
			if(rl==0) continue;
			used+=rl; 
			edge[i].l-=rl;
			edge[i^1].l+=rl;
			if(used==flow) break;//满了 
			//return rl;
		}
	}
	return used; 
}
 
ll dic()
{
	while(bfs())
	{
		vis=1;
		while(vis)
		{
		    vis=0;
			dfs(s,inf);	
		} 
	}
	return ans;
}
int main()
{
	
	memset(head,-1,sizeof head);
	n=read();m=read();t=read();s=read();
	s+=n;//拆点的话就不要忘了更新终点!!! 
	for(int i=1;i<=n;++i)
	{
		add(i,i+n,1);
		add(i+n,i,0);//拆点小trick 
	}
	for(int i=1;i<=m;++i)
	{
		a=read();b=read();
		add(a+n,b,inf);
		add(b,a+n,0);
		add(b+n,a,inf);
		add(a,b+n,0);
	}
	ll k=dic();
	cout<<k<<endl;
	return 0;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值