BZOJ1797: [Ahoi2009]Mincut最小割-tarjan缩点&&SAP求最大流

有任何问题欢迎留言或私聊

题目链接:BZOJ1797-点击做题


题目:

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表示否)。 同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。



题意和思路:

1。题目直接说了跑最大流,所以第一步就直接用板子跑最大流(最小割)就行了,我最大流的板子在这里
2。虽然你求出来了最小割答案,但是对于这个答案经过了多少条不同路径的还不清楚!不过我们知道每条边流量flow的变化量和它反向边流量flow的边化量。这点很重要。
3。知道了跑完最大流之后的流量,你就直接在这个残量网络上用tarjan缩点
4。遍历每条边(不遍历反向边)。下面分情况讨论:

  • 1.如果其反向边没有流量变化,代表最大流没有经过这条边,直接输出0 0;
  • 2.如果这条边的两个端点缩点后在一个强连通分量中,就是说缩点后为同一点编号,那么也输出0 0;
  • 3.如果两个端点缩点后,一个和S点在同一强连通分量另一个和T点在同一强连通分量,则输出1 1;反之输出1 0;

AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#define fuck(x) printf("*%d\n",x )
using namespace std;
typedef long long LL;
const int MAXN = 5005;
const int N = 5005;
const int X = 0x7fffffff;
const int INF = 0x3f3f3f3f ;
char s[MAXN];
struct lp{
    int u,v, next;
    LL cap;
} cw[MAXN * 100],temp[MAXN*100];
int n, m, vs, vt, NE, NV;
int head[MAXN],pre[MAXN],cur[MAXN],level[MAXN],gap[MAXN];
int vis[N],Index;
int dfn[N],low[N];
int qltNum;
int qltMap[N];
stack<int> st;
inline void Insert(int u, int v, LL cap){
    cw[NE].v = v;
    cw[NE].cap = cap;
    cw[NE].next = head[u];
    cw[NE].u=u;
    head[u] = NE++;
    cur[u] = head[u];
    cw[NE].v = u;
    cw[NE].u=v;
    cw[NE].cap = 0;
    cw[NE].next = head[v];
    head[v] = NE++;
    cur[v] = head[v];
}
LL SAP(int vs,int vt){
    memset(level,0,sizeof(level));
    memset(pre,-1,sizeof(pre));
    memset(gap,0,sizeof(gap));
    int u=pre[vs]=vs;
    LL maxflow=0,aug=-1;
    gap[0]=NV;
    while(level[vs]<NV){
        int flag=0;
        for(int &i=cur[u];i!=-1;i=cw[i].next){
            int v=cw[i].v;
            if(cw[i].cap&&level[u]==level[v]+1){
                aug==-1?(aug=cw[i].cap):(aug=min(aug,cw[i].cap));
                pre[v]=u;
                u=v;
                if(v==vt){
                    maxflow+=aug;
                    for(u=pre[v];v!=vs;v=u,u=pre[u]){
                        cw[cur[u]].cap-=aug;
                        cw[cur[u]^1].cap+=aug;
                    }
                    aug=-1;
                }
                flag=1;
                if(flag)break;
            }
        }
        if(flag)continue;
        int minlevel=NV;
        for(int i=head[u];i!=-1;i=cw[i].next){
            int v=cw[i].v;
            if(cw[i].cap&&minlevel>level[v]){
                cur[u]=i;
                minlevel=level[v];
            }
        }
        if((--gap[level[u]])==0)break;
        level[u]=minlevel+1;
        gap[level[u]]++;
        u=pre[u];
    }
    return maxflow;
} 
void dfs(int u,int fa){
    dfn[u] = low[u] = ++Index;
    vis[u]=1;int v;
    st.push(u);
    for(int i=head[u];~i;i=cw[i].next){
        v=cw[i].v;
        if(cw[i].cap<=0)continue;
        if(!vis[v]){
            dfs(v,u);
            low[u]=min(low[u],low[v]);
            //if(low[v] > dfn[u]) bridge
        }else if(vis[v]==1){
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(low[u]==dfn[u]){
        qltNum++;
        do{
            v=st.top();st.pop();
            vis[v]=2;
            qltMap[v]=qltNum;
        }while(v!=u);
    }
}
void tarjan(){
    for(int i=1;i<=n;++i){
        if(!vis[i]){
            dfs(i,0);
        }
    }
}
void init(){
    qltNum=0;while(!st.empty())st.pop();
    Index=0;memset(vis,0,sizeof(vis));
    memset(qltMap,0,sizeof(qltMap));
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
}
int main(){
    int S,T;
    while(~scanf("%d%d%d%d",&n,&m,&S,&T)) {
        //n n
        NV=n;NE=0;
        vs=S,vt=T;
        memset(head, -1, sizeof(head));
        memset(cur, -1, sizeof(cur));
        for(int i=0;i<m;++i){
            int x,y;
            LL z;
            scanf("%d%d%lld",&x,&y,&z);
            Insert(x,y,z);
        }
        LL ans =SAP(vs,vt);
        init();
        tarjan();
        int qltS=qltMap[S],qltT=qltMap[T];
        for(int i=0;i<NE;i+=2){
            if(cw[i^1].cap>0){
                if(qltMap[cw[i].u]!=qltMap[cw[i].v]){
                    if((qltMap[cw[i].u]==qltS&&qltMap[cw[i].v]==qltT)||(qltMap[cw[i].u]==qltT&&qltMap[cw[i].v]==qltS)){
                        printf("1 1\n");
                    }else printf("1 0\n");
                }else{
                    printf("0 0\n");
                }
            }else printf("0 0\n");
        }

    }
    return 0;
}
/*
1 0
1 0
0 0
1 0
0 0
1 0
1 0
最小割唯一性判定

jcvb:

在残余网络上跑tarjan求出所有SCC,记id[u]为点u所在SCC的编号。显然有id[s]!=id[t](否则s到t有通路,能继续增广)。

①对于任意一条满流边(u,v),(u,v)能够出现在某个最小割集中,当且仅当id[u]!=id[v];
②对于任意一条满流边(u,v),(u,v)必定出现在最小割集中,当且仅当id[u]==id[s]且id[v]==id[t]。

①
<==将每个SCC缩成一个点,得到的新图就只含有满流边了。那么新图的任一s-t割都对应原图的某个最小割,从中任取一个把id[u]和id[v]割开的割即可证明。
②
<==:假设将(u,v)的边权增大,那么残余网络中会出现s->u->v->t的通路,从而能继续增广,于是最大流流量(也就是最小割容量)会增大。这即说明(u,v)是最小割集中必须出现的边。
*/

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
#include <stack>
using namespace std;
  
const int maxn = 1007;
typedef long long LL;
struct Edge { //栈中边的结构
    int u, v;
    Edge(int uu, int vv)
    {
        u = uu;
        v = vv;
    }
};
stack<Edge> s;
  
struct edge { //链式前向星建图的边结构
    int v, next;
} edges[maxn];
  
int n, m;       //节点的数目,无向边的数目
int e, head[maxn];
int pre[maxn];           //第一次访问的时间戳
int dfs_clock;           //时间戳
int iscut[maxn];         //标记节点是否为割顶
int bcc_cnt;             //点_双连通分量的数目
int bccno[maxn];         //节点属于的点_双连通分量的编号
vector<int> bcc[maxn];   //点_双连通分量
  
void addedges(int u, int v) //加边
{
    edges[e].v = v;
    edges[e].next = head[u];
    head[u] = e++;
    edges[e].v = u;
    edges[e].next = head[v];
    head[v] = e++;
}
  
int dfs(int u, int fa)
{
    int lowu = pre[u] = ++dfs_clock;
    int child = 0;
    for(int i = head[u]; i != -1; i = edges[i].next) {
        int v = edges[i].v;
        Edge e = (Edge) {u, v};
        if(!pre[v]) {
            s.push(e);
            child++;
            int lowv = dfs(v, u);
            lowu = min(lowu, lowv); //用后代更新lowu
            if(lowv >= pre[u]) {   //找到了一个子树满足割顶的条件
                iscut[u] = 1;
                bcc_cnt++;
                bcc[bcc_cnt].clear();
                for(;;) {          //保存bcc信息
                    Edge x = s.top(); s.pop();
                    if(bccno[x.u] != bcc_cnt) {bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt;}
                    if(bccno[x.v] != bcc_cnt) {bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt;}
                    if(x.u == u && x.v == v) break;
                }
            }
        } else if(pre[v] < pre[u] && v != fa) { //用反向边更新lowu
            s.push(e);
            lowu = min(lowu, pre[v]);
        }
    }
    if(fa < 0 && child == 1) iscut[u] = 0;    //对于根节点若只有一个子树则不是割顶
    return lowu;
}
  
void init()
{
    memset(pre, 0, sizeof(pre));
    memset(iscut, 0, sizeof(iscut));
    memset(head, -1, sizeof(head));
    memset(bccno, 0, sizeof(bccno));
    for(int i=0;i<=maxn;++i)bcc[i].clear();
  
    e = 0; dfs_clock = 0; bcc_cnt = 0;
    n=0;
}
int main()
{
    int u, v; int tim = 1;
    while(~scanf("%d", &m) && m) {
        init();
        for(int i = 0; i < m; i++) {
            scanf("%d%d", &u, &v);
            addedges(u, v);
            n=max(max(u,v),n);
        }
        printf("Case %d: ", tim++ );
        for(int i=1;i<=n;++i){
            if(!pre[i]) dfs(i, -1);
        }
        int flag=1;
        LL ans_cut=0LL,ans_num=0LL,ans_t=1LL;
        for(int i = 1; i <= bcc_cnt; i++) {
            int cnt_cut=0,num_size=bcc[i].size();
            for(int j = 0; j < bcc[i].size(); j++){
                if(iscut[bcc[i][j]])cnt_cut++;
            }
            if(cnt_cut>=2)continue;
            if(cnt_cut==0){
                ans_cut+=(num_size*(num_size-1)/2),ans_num+=2LL;
                //if(num_size==2)ans_cut++;
            }else {
                ans_t*=(num_size-1);
                ans_num++;
                flag=0;
            }
        }
        if(flag)ans_t=0;
        printf("%lld %lld\n",ans_num,ans_cut+ans_t);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值