POJ - 1815 - Friendship(网络流-拆点建图)

题目:POJ-1815

题意:在一个给定的无向图中至少应该去掉几个顶点才干使得s和t不联通。

题解:

假设s和t直接相连输出no answer。

把每一个点拆成两个点v和v'',这两个点之间连一条权值为1的边(残余容量)

v和v''各自是一个流进的点。一个流出的点。

依据求最小割的性质。权值小的边是可能被选择的(断开的)。

加入源点st=0和汇点en=2*n+1,源点与s连权值为inf的边。t''与汇点连权值为inf的边。

s与s'',t与t''连权值为inf的边,这样保证自己和自己是不会失去联系的。

假设i和j有边相连。则i''和j连权值为inf的边。j''与i连权值为inf的边。

这样建图后跑最大流,求得的流量即为点的个数。

然后编号从小到大枚举每一个点。尝试去掉这个点(即仅仅进不出)。又一次建图再跑最大流。

看最大流是否会减小。假设减小了,就是要去掉的点,输出就好了,然后更新当前的最大流。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 100005
#define INF 0x7fffffff
using namespace std;
int n,s,t,S,T,c;
int head[N],cur[N],deep[N],mp[205][205];
bool vis[N];
struct ljh
{
	int next,to,w;
}e[N*2];
inline void add(int x,int y,int z)
{
	e[c].next=head[x];
	e[c].w=z;
	e[c].to=y;
	head[x]=c++;
	e[c].next=head[y];
	e[c].w=0;
	e[c].to=x;
	head[y]=c++;
}
void build()
{
	memset(head,-1,sizeof(head));
	c=0;
	add(S,s,INF);
	add(t+n,T,INF);
	for(int i=1;i<=n;i++)
	{
		if(vis[i]==0&&i!=s&&i!=t)add(i,i+n,1);
		for(int j=1;j<=n;j++)
		{
			if(mp[i][j]==1)
				add(i+n,j,INF);
		}
	}
	add(s,s+n,INF);
	add(t,t+n,INF);
}
bool bfs(int S,int T)
{
    memset(deep,0,sizeof(deep));
    deep[S]=1;
    queue<int>q;
    q.push(S);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int nex=e[i].to;
            if(deep[nex]==0&&e[i].w>0)
            {
                deep[nex]=deep[x]+1;
                q.push(nex);
            }
        }
    }
    return deep[T];
}
int dfs(int x,int T,int maxflow)
{
    if(x==T)return maxflow;
    int ans=0;
    for(int i=cur[x];i!=-1;i=e[i].next)
    {
        int nex=e[i].to;
        if(deep[nex]!=deep[x]+1||ans>=maxflow||e[i].w<=0)continue;
        cur[x]=i;
        int k=dfs(nex,t,min(e[i].w,maxflow-ans));
        e[i].w-=k;
        e[i^1].w+=k;
        ans+=k;
    }
    if(ans==0)deep[x]=-2;
    return ans;
}
int Dinic(int S,int T)
{
	int ans=0;
	while(bfs(S,T))
	{
		memcpy(cur,head,sizeof(head));
		ans+=dfs(S,T,INF);
	}
	return ans;
}
int main()
{
	while(~scanf("%d%d%d",&n,&s,&t))
	{
		S=0;
		T=2*n+1;
		memset(vis,0,sizeof(vis));
		memset(mp,0,sizeof(mp));
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				scanf("%d",&mp[i][j]);	
		if(mp[s][t]==1){ printf("NO ANSWER!\n");continue;}
		build();
		int ans=Dinic(S,T);
		printf("%d\n",ans);
		if(ans==0)continue;
		int tmp=ans;
		for(int i=1;i<=n;i++)
		{
			if(i==s||i==t)continue;
			vis[i]=1;
			build();
			int sum=Dinic(S,T);
			// cout<<sum<<endl;
			if(sum<tmp)
			{
				tmp=sum;
				printf("%d ",i);
			}
			else vis[i]=0;
			if(tmp==0){break;}
		}
		printf("\n");
	}
}
/*
7 1 7
1 1 0 1 0 0 0
1 1 1 0 0 0 0
0 1 1 1 0 0 1
1 0 1 1 1 1 0
0 0 0 1 1 0 1
0 0 0 1 0 1 1
0 0 1 0 1 1 1
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值