[bzoj1797][AHOI2009]最小割

本文详细解析了一个典型的最小割问题实例——Ahoi2009 Mincut最小割。介绍了如何通过构建网络流模型解决该问题,包括使用ISAP算法求最小割及利用Tarjan算法进行连通性分析,从而判断每条边在最小割中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1797: [Ahoi2009]Mincut 最小割

Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 1571 Solved: 671
[Submit][Status][Discuss]
Description

A,B两个国家正在交战,其中A国的物资运输网中有N个中转站,M条单向道路。设其中第i (1≤i≤M)条道路连接了vi,ui两个中转站,那么中转站vi可以通过该道路到达ui中转站,如果切断这条道路,需要代价ci。现在B国想找出一个路径切断方案,使中转站s不能到达中转站t,并且切断路径的代价之和最小。 小可可一眼就看出,这是一个求最小割的问题。但爱思考的小可可并不局限于此。现在他对每条单向道路提出两个问题: 问题一:是否存在一个最小代价路径切断方案,其中该道路被切断? 问题二:是否对任何一个最小代价路径切断方案,都有该道路被切断? 现在请你回答这两个问题。

Input

第一行有4个正整数,依次为N,M,s和t。第2行到第(M+1)行每行3个正 整数v,u,c表示v中转站到u中转站之间有单向道路相连,单向道路的起点是v, 终点是u,切断它的代价是c(1≤c≤100000)。 注意:两个中转站之间可能有多条道路直接相连。 同一行相邻两数之间可能有一个或多个空格。

Output

对每条单向边,按输入顺序,依次输出一行,包含两个非0即1的整数,分 别表示对问题一和问题二的回答(其中输出1表示是,输出0表示否)。 同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。

Sample Input

6 7 1 6

1 2 3

1 3 2

2 4 4

2 5 1

3 5 5

4 6 2

5 6 3

Sample Output

1 0

1 0

0 0

1 0

0 0

1 0

1 0

HINT

设第(i+1)行输入的边为i号边,那么{1,2},{6,7},{2,4,6}是仅有的三个最小代价切割方案。它们的并是{1,2,4,6,7},交是 。 【数据规模和约定】 测试数据规模如下表所示 数据编号 N M 数据编号 N M 1 10 50 6 1000 20000 2 20 200 7 1000 40000 3 200 2000 8 2000 50000 4 200 2000 9 3000 60000 5 1000 20000 10 4000 60000

这种类型的网络流之前没大做过,感觉和建模的那种完全不是一种感觉。
先来想怎样判断可能出现的情况,我开始以为只要跑一边最小割以后,那些漫流的边就是可能出现的。但是好像随便出一些数据就卡掉了。
我们知道本质不同的最小割有n-1个,这些最小割把整个图分成了很多个不想交的集合。根据这个结论和上面的想法,我们就可以想出来,跑完最小割后的所有连通块之间的满流边就是要求的。
然后再看第二问,我们已经有了所有的连同块了,那么我们想一下这之间有哪些边是不能被替代的。是那些连接起点和终点的边。为什么呢?因为如果这条边不是直接连接的起点和终点而是从中间经过一些点之后再到达的终点。那么这条边就可以南北这些边中的一些边替代掉了,所以有用的只是那些直接连接起点和终点的边。
在找连通块的时候我们可以用tarjan。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 0x7fffffff
const int N=4010;
const int M=150000;
struct S{int st,en,va;}aa[M],l[M];
int n,m,s,t,point[N],next[M],tot=1,dis[N],gap[N],pre[N],cur[N];
int lowlink[N],dfs[N],stack[N],now[N],sccno[N],top,sccn,dfsn;
inline int in(){
    int x=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline void add(int x,int y,int z){
    tot+=1;next[tot]=point[x];point[x]=tot;
    aa[tot].st=x;aa[tot].en=y;aa[tot].va=z;
    tot+=1;next[tot]=point[y];point[y]=tot;
    aa[tot].st=y;aa[tot].en=x;aa[tot].va=0;
}
inline void ISAP(int ss,int tt){
    bool f;
    int i,u,minn,j;
    for(i=1;i<=n;++i) cur[i]=point[i];
    gap[0]=n;u=ss;
    while(dis[ss]<=n){
        f=false;
        for(i=cur[u];i;i=next[i])
          if(aa[i].va>0&&dis[aa[i].en]+1==dis[u]){
            cur[u]=i;f=true;break;
          }
        if(f){
            pre[u=aa[i].en]=i;
            if(u==tt){
                minn=inf;
                for(i=u;i!=ss;i=aa[pre[i]].st)
                  minn=min(minn,aa[pre[i]].va);
                for(i=u;i!=ss;i=aa[pre[i]].st){
                    aa[pre[i]].va-=minn;
                    aa[pre[i]^1].va+=minn;
                }
                u=ss;
            }
        }
        else{
            --gap[dis[u]];
            if(!gap[dis[u]]) return ;
            j=n*2;cur[u]=point[u];
            for(i=point[u];i;i=next[i])
              if(aa[i].va) j=min(j,dis[aa[i].en]);
            ++gap[dis[u]=j+1];
            if(u!=ss) u=aa[pre[u]].st;
        }
    }
}
inline void tarjan(int x){
    int i,u;
    now[x]=lowlink[x]=++dfsn;
    stack[++top]=x;
    for(i=point[x];i;i=next[i]){
        if(aa[i].va==0) continue;
        u=aa[i].en;
        if(!now[u]){
            tarjan(u);
            lowlink[x]=min(lowlink[u],lowlink[x]);
        }
        else if(!sccno[u]) lowlink[x]=min(lowlink[x],now[u]);
    }
    if(lowlink[x]==now[x]){
        sccn+=1;
        while(1){
            u=stack[top];
            top-=1;
            sccno[u]=sccn;
            if(x==u) break;
        }
    }
}
int main(){
    int i,j,x,y,z,ans1,ans2;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(i=1;i<=m;++i){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        l[i].st=x;l[i].en=y;l[i].va=z;
    }
    ISAP(s,t);
    for(i=1;i<=n;++i)
      if(!now[i]) tarjan(i);
    for(i=1;i<=m;++i){
        ans1=ans2=0;
        x=l[i].st,y=l[i].en;
        if(sccno[x]!=sccno[y]){
            if(aa[i<<1].va==0) ans1=1;
            if(sccno[x]==sccno[s]&&sccno[y]==sccno[t]) ans2=1;
        }
        printf("%d %d\n",ans1,ans2);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值