BZOJ1797: [Ahoi2009]Mincut 最小割 【网络流】

题目传送门

题目描述:

网络流,n个点,m条有向边组成一张图,每条边有权值(流量)ci,给出源点,汇点,对于每条边,回答两个问题:

  1. 是否存在一个最小割包含这条边
  2. 是否任意一个最小割都包含这条边
题目分析:

最小割的可行边(两端点记为u,v):被某一种最小割方案包含的边。

充要条件:1.满流。2.在残余网络中找不到u到v的路径。

  • 若不满流显然可以找到另一条更小的满流的限制流量的边。
  • 若满流但在残余网络中能找到u到v的路径,那么需要同时割掉这条边和残余网络中路径上某条边才能实现“割”,而这两条边上的流量和一定等于其他某条或某几条满流的边的流量和,又因为位于残余网络上的那条边一定不满流,割值大于它的当前流量,所以这种割法不是最小割。
  • 求法:
    由于该边满流,它的反向边一定存在于残余网络中,反向边与其他u到v的路径会构成SCC。
    在残余网络中tarjan求SCC,u,v两点在同一SCC中说明残余网络中存在u到v的路径。

最小割的必须边(不考虑容量为0的边):一定在最小割中的边。

充要条件:1.满流。2.残余网络中源点能到u,v能到汇点。

  • 一条边是必须边,当且仅当扩大它的容量后能增大最大流。

  • 求法:在残余网络中tarjan求SCC,源点到u在一个SCC中,v与汇点在一个SCC中

so,这道题的做法就很明显了,随意跑一个最大流,得到残余网络,再tarjan求一发SCC,最后for循环判断每条边即可。

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define maxn 4005
#define maxm 120006
using namespace std;
int n,m,S,T,dis[maxn];
int fir[maxn],nxt[maxm],to[maxm],c[maxm],tot=1;//设为-1的话邻接表的循环终止条件要改
inline void line(int x,int y,int z){
	nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,c[tot]=z;
	nxt[++tot]=fir[y],fir[y]=tot,to[tot]=x,c[tot]=0;
}
queue<int>q;
bool bfs()
{
	memset(dis,0,sizeof dis);
	dis[T]=1,q.push(T);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=fir[u];i;i=nxt[i]) if(c[i^1]&&!dis[to[i]]){
			dis[to[i]]=dis[u]+1;
			q.push(to[i]);
		}
	}
	return dis[S];
}
int dinic(int u,int aug)//单路増广会TLE
{
	if(u==T) return aug;
	int need=aug,delta;
	for(int i=fir[u];i;i=nxt[i]) if(dis[u]==dis[to[i]]+1&&c[i]){
		delta=dinic(to[i],min(need,c[i]));
		c[i]-=delta,c[i^1]+=delta;
		if(!(need-=delta)) return aug;
	}
	return aug-need;
}
int dfn[maxn],low[maxn],stk[maxn],scc[maxn],top,tim,scnt;
void tarjan(int u)//这里不能判u!=fa,本来就是有向边
{
	dfn[u]=low[u]=++tim;
	stk[++top]=u;
	for(int i=fir[u],v;i;i=nxt[i]) if(c[i]){
		if(!dfn[(v=to[i])]) tarjan(v),low[u]=min(low[u],low[v]);
		else if(!scc[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		++scnt;
		do scc[stk[top]]=scnt; while(stk[top--]!=u);
	}
}
int main()
{
	int x,y,z;
	scanf("%d%d%d%d",&n,&m,&S,&T);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&x,&y,&z),line(x,y,z);
	while(bfs()) dinic(S,0x3f3f3f3f);
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	for(int i=2;i<=tot;i+=2)
		if(c[i]||scc[to[i]]==scc[to[i^1]]) puts("0 0");
		else printf("1 %d\n",scc[S]==scc[to[i^1]]&&scc[to[i]]==scc[T]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值