题意:给出n个点在A处和B处消耗的时间,再给出其中m对点在不同处时所需要的时间,求能划分的最短时间。
这题是我第一次学习sap算法,其中还由于数据量大,用到了邻接表,学习模板加编程用了三天时间才写出这题,总算也是松了口气。初次接触网络流,找不到适合的资料,翻了十几篇博文,差点放弃。
这题如何构图是关键,能想到用网络流也是关键。把A处看做源点,B处看成汇点,将n个点分别与两点做有向边,再将剩下m对点之间做双向边。注意在构造单向边时既要构造从弧尾到弧头的边,权值为消耗的时间,也要构造弧头到弧尾的边,权值为0。这样做的好处是在构造残余网络时可以很方便的得出反向边的地址。
划分的最短时间也就是此图的最小割,而最小割等于最大流,用sap求出其最大流。
#include <iostream> #include<cstdio> #include<cstring> #include<cmath> #define INF 0x7fffffff #define N 300000 using namespace std; struct node { int to,next,w; }e[N*8]; int cur[N],gap[N],nn,pre[N],head[N],dis[N],cnt; void add(int u,int v,int w ) { e[cnt].to=v; e[cnt].next=head[u]; e[cnt].w=w; head[u]=cnt++; e[cnt].to=u; e[cnt].w=0; e[cnt].next=head[v]; head[v]=cnt++; } int sap(int s,int t) { for(int i=0;i<nn;i++) { cur[i]=head[i]; gap[i]=dis[i]=0; } int ans=0,mmin=INF,u; u=pre[s]=s; gap[s]=nn; while(dis[s]<nn) { int flag=0; for(int &i=cur[u];i!=-1;i=e[i].next) { int v=e[i].to; if(dis[u]==dis[v]+1&&e[i].w) { flag=1; mmin=min(mmin,e[i].w); pre[v]=u; u=v; if(u==t) { ans+=mmin; while(u!=s) { u=pre[u]; e[cur[u]].w-=mmin; e[cur[u]^1].w+=mmin; } mmin=INF; } break; } } if(flag) continue; int md=nn; for(int i=head[u];i!=-1;i=e[i].next) { int v=e[i].to; if(e[i].w&&dis[v]<md) { md=dis[v]; cur[u]=i; } } if(--gap[dis[u]]==0) return ans; gap[dis[u]=md+1]++; u=pre[u]; } return ans; } int main() { int n,m; cin>>n>>m; memset(head,-1,sizeof(head)); cnt=0; for(int i=1;i<=n;i++) { int v,u; scanf("%d%d",&v,&u); add(0,i,v),add(i,n+1,u); } for(int i=0;i<m;i++) { int v,u,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w),add(v,u,w); } nn=n+2; cout<<sap(0,n+1)<<endl; }