P4126 [AHOI2009]最小割 网络流

  

A,B两个国家正在交战,其中A国的物资运输网中有NN个中转站,MM条单向道路。设其中第i (1≤i≤M)i(1iM)条道路连接了v_i,u_ivi,ui两个中转站,那么中转站v_ivi可以通过该道路到达u_iui中转站,如果切断这条道路,需要代价c_ici

现在B国想找出一个路径切断方案,使中转站ss不能到达中转站tt,并且切断路径的代价之和最小。

小可可一眼就看出,这是一个求最小割的问题。但爱思考的小可可并不局限于此。现在他对每条单向道路提出两个问题:

  • 问题一:是否存在一个最小代价路径切断方案,其中该道路被切断?
  • 问题二:是否对任何一个最小代价路径切断方案,都有该道路被切断?

问题1  判定是否为最小割的可行边

问题2  判定是否为最小割的必须边

#include<bits/stdc++.h>
using namespace std;
inline int read(){//读优 
    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;
}
int n,m,s,t;
struct Edge{
    int u,v,w,nxt;
}e[120010];
int head[4010],cnt=1;//注意:cnt必须从1开始,因为加边是n和n+1,偶数和偶数+1可以通过异或转化,具体请自行推导 
inline void add(int u,int v,int w){//前向星加边 
    e[++cnt].u=u;
    e[cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
int dis[4010];
int bfs(){
    queue<int>q;//广搜队列 
    q.push(s);
    memset(dis,-1,sizeof(dis));//所有编号赋初值 
    dis[s]=0;//初始原点编号,让其可以朝下分层 
    while(!q.empty()){
        int u=q.front();q.pop();//取出队首 
        for(int i=head[u];i;i=e[i].nxt){
            if(e[i].w>0&&dis[e[i].v]==-1){//如果此点可以流并且它没有被编号过 
                dis[e[i].v]=dis[u]+1;//分层编号
                q.push(e[i].v);//入队 
                if(e[i].v==t)return 1;//如果将汇点编完号,返回,示意dinic继续找增广 
            }
        }
    }
    return 0;//找不到一条可行流,示意dinic返回 
}
int dfs(int x,int f){
    if(x==t||f==0)return f;//如果搜到了增广路或者没有流量走不下去 
    int used=0;//定义已经流出的流量 
    for(int i=head[x];i;i=e[i].nxt){
        if(e[i].w>0&&dis[e[i].v]==dis[x]+1){//如果此边能走通并且编号正确(根据dinic,只有是下一编号才能走通) 
            int k=dfs(e[i].v,min(e[i].w,f));//向下流,注意处理流量!!!一定要二者最小的! 
            if(k==0)continue;//无法流通,继续处理下一条边 
            used+=k;f-=k;//减少剩余流量,增加流出流量 
            e[i].w-=k;e[i^1].w+=k;//对正向弧和反向弧做流量处理
            if(f==0)break;//剩余流量为0,结束 
        }
    }
    if(used==0)dis[x]=-1;//无法下流,使其退出分层,下次不用再走,否则浪费效率 
    return used;//返回可行流的最大流量 
}
int dinic(){
    int flow=0;//最大流 
    while(bfs())
        flow+=dfs(s,0x7fffffff);//加上每次增广可继续下流的流量 
    return flow;
}
int dfn[4010],low[4010],vis[4010],scc[4010],num,cntt;
stack<int>st;
void tarjan(int u){//tarjan模板,判scc分量,见模板题,不做详细解释 
    st.push(u);vis[u]=1;
    dfn[u]=low[u]=++cntt;
    for(int i=head[u];i;i=e[i].nxt){
        if(e[i].w==0)continue;//注意,满流时无法继续,是本题的关键点 
        if(dfn[e[i].v]==0){
            tarjan(e[i].v);
            low[u]=min(low[u],low[e[i].v]);
        }
        else if(vis[e[i].v]==1)
            low[u]=min(dfn[e[i].v],low[u]);
    }
    if(dfn[u]==low[u]){
        ++num;
        while(1){
            int top=st.top();st.pop();
            vis[top]=0;scc[top]=num;
            if(top==u)break;
        }
    }
}
int main(){
    n=read();m=read();s=read();t=read();
    for(int i=1;i<=m;++i){
        int u,v,w;
        u=read();v=read();w=read();
        add(u,v,w);add(v,u,0);//加边,正向弧和反向弧 
    }
    int flow=dinic();//尽管flow并没有用,但调试较为方便 
    for(int i=1;i<=n;++i)
        if(scc[i]==0)
            tarjan(i);//如果没有判过scc,跑一遍,求出它属于的联通块
    //判断方法和公式见前面 
    for(int i=2;i<cnt;i+=2){//这样才能跳到每一条正向边 
        int u=e[i].u,v=e[i].v;
        if(e[i].w==0&&scc[u]!=scc[v]){//记得判断满流
            printf("1 ");
            if(scc[u]==scc[s]&&scc[v]==scc[t])printf("1");
            else printf("0");
        }
        else printf("0 0");
        printf("\n");
    }
    return 0;
} 
View Code

猜你喜欢

转载自www.cnblogs.com/bxd123/p/11252216.html