原题:
传送门
这是一个网络扩容问题,就是说,给你一个网络,然后看网络的最大流是否大于某个阈值。如果比阈值小,能否只通过修改一条边的容量,来使得这个网络的最大流大于阈值,并且输出所有可行的扩容边。
题目比较裸,但是实现也并不简单。首先,扩容的边可以有多条,如果枚举每一条边,把它扩容,跑一次最大流,这样总共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;
}