Uva 11248 Frequency Hopping (最大流+残余网络)

        原题: 传送门
        这是一个网络扩容问题,就是说,给你一个网络,然后看网络的最大流是否大于某个阈值。如果比阈值小,能否只通过修改一条边的容量,来使得这个网络的最大流大于阈值,并且输出所有可行的扩容边。
        题目比较裸,但是实现也并不简单。首先,扩容的边可以有多条,如果枚举每一条边,把它扩容,跑一次最大流,这样总共M条边,跑M遍最大流,明显超时。所以说肯定不是枚举每一条边作为扩容边,那么选取什么边呢?我们看下图。
                           
        如果我们修改的便不是最小割中的边,那么显然最大流还是受限与最小割中的边,不会改变,所以能够改变的肯定是最小割中的边。于是,我们求出最小割中的边,然后枚举每一条边去扩容。然而,这并不是这么简单。如果扩容之后,重新跑最大流,那么恭喜你TLE。因为这样本质还是跑了很多边最大流,只不过次数少了点而已,第一次跑了之后的残余网络完全没用了。
        我们发现,每次最大流都一个重复的部分,那就是第一次跑的残余网络。那么既然重复,我们为什么不直接在残余网络上跑最大流呢?相当于在参与网络上增广,求出的最大流与第一次的最大流相加,就是扩容之后总的最大流。如此一来,相当于整个图少了很多边,速度得到了大的提升。另外,还可以加一个小优化,如果到某个时刻求出的最大流已经超过了我们需要的大小,那么便可以退出。
        关于具体写法,如果你的模板中只是保存了容量cap而没有保存每条边的当前流量flow,那么就会麻烦一点,每次清空的时候不是简单的清零,而是把容量cap修改为残余网络时的状态。然后这个也要注意,设置一个change数组,记录那些边在残余网络中增广的时候被修改了。这样复原的时候只复原这些发生改变的边,又是一个优化。然后这题我用了ISAP写,但是速度还是很慢,我看到有人写的ISAP比我快好多……具体见代码:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define M 20010

using namespace std;

typedef pair<int,int> P;
vector<int> cut,change;
int n,m,delta,tot;
P ans[M];

namespace ISAP
{
    int H[M],d[M],cur[M],pre[M],gap[M],Q[M],RC[M];
    struct Edge{int u,v,c,n;} E[M];
    int nv,flow,head,tail,cntE,f;

    void init(){cntE=0; memset(H,-1,sizeof(H));}

    void addedge(int u,int v,int c)
    {
        E[cntE]=Edge{u,v,c,H[u]}; H[u]=cntE++;
        E[cntE]=Edge{v,u,0,H[v]}; H[v]=cntE++;
    }

    void revbfs(int s,int t)
    {
        head=tail=0 ;
        memset(d,-1,sizeof(d));
        memset(gap,0,sizeof(gap));
        Q[tail++]=t;d[t]=0;gap[d[t]]=1;
        while (head!=tail)
        {
            int u=Q[head++];
            for (int i=H[u];~i;i=E[i].n)
            {
                int v=E[i].v; if (~d[v]) continue;
                d[v]=d[u]+1; gap[d[v]]++; Q[tail++]=v;
            }
        }
    }

    int isap(int s,int t,int limit)
    {
        memcpy(cur,H,sizeof(cur)); nv=t;
        flow=0; revbfs(s,t); int u=pre[s]=s,i;
        while (d[s]<nv)
        {
            if (u==t)
            {
                f=INF;
                for (i=s;i!=t;i=E[cur[i]].v)
                    if (f>E[cur[i]].c) f=E[cur[i]].c,u=i;
                flow += f; if (flow>=limit) return flow;		//如果当前流量大于限制,则退出
                for (i=s;i!=t;i=E[cur[i]].v)
                {
                    E[cur[i]].c-=f,E[cur[i]^1].c+=f;
                    change.push_back(cur[i]);				//修改的边加入change
                }
            }
            for (i=cur[u];~i;i=E[i].n)
                if (E[i].c&&d[u]==d[E[i].v]+1) break ;
            if (~i) cur[u]=i,pre[E[i].v]=u,u=E[i].v;
            else
            {
                if (0==--gap[d[u]]) break ;
                int minv=nv,v;
                for (int i=H[u];~i;i=E[i].n)
                {
                    v=E[i].v;
                    if (E[i].c&&minv>d[v]) minv=d[v],cur[u]=i;
                }
                d[u]=minv+1; gap[d[u]]++; u=pre[u];
            }
        }
        return flow ;
    }

    void getcut()
    {
        int zero=0;
        for(int i=0;i<n;i++)
            if (gap[i]==0) {zero=i;break;}
        for(int i=0;i<cntE;i+=2)
        {
            RC[i]=E[i].c; RC[i^1]=E[i^1].c;
            if (d[E[i].u]>=zero&&d[E[i].v]<zero) cut.push_back(i);
        }
    }

    void recover()
    {
        for(int i=0;i<change.size();i++)				//每次只复原change中改变的边
        {
            E[change[i]].c=RC[change[i]];
            E[change[i]^1].c=RC[change[i]^1];
        }
        change.clear();
    }
}

int main()
{
    int T_T=0,Flow;
    while(~scanf("%d%d%d",&n,&m,&Flow))
    {
        ISAP::init(); tot=0;
        if (n+m+Flow==0) return 0;
        printf("Case %d: ",++T_T);
        for(int i=1;i<=m;i++)
        {
            int u,v,cap;
            scanf("%d%d%d",&u,&v,&cap);
            ISAP::addedge(u,v,cap);
        }
        int flow=ISAP::isap(1,n,Flow);
        if (flow>=Flow){puts("possible");continue;}
        delta=Flow-flow;
        ISAP::getcut();
        for(int i=0;i<cut.size();i++)
        {
            ISAP::recover();
            int u=ISAP::E[cut[i]].u;
            int v=ISAP::E[cut[i]].v;
            ISAP::E[cut[i]].c=delta;
            change.push_back(cut[i]);
            if (ISAP::isap(1,n,delta)>=delta) ans[++tot]=P{u,v};		//直接在残余网络中继续增广
        }
        if (tot)
        {
            printf("possible option:");
            sort(ans+1,ans+tot+1);
            tot=unique(ans+1,ans+tot+1)-ans-1;
            for(int i=1;i<tot;i++)
                printf("(%d,%d),",ans[i].first,ans[i].second);
            printf("(%d,%d)\n",ans[tot].first,ans[tot].second);
        } else puts("not possible");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值