PARTI 最大流

例1:bzoj 3931

Description

路由是指通过计算机网络把信息从源地址传输到目的地址的活动,也是计算机网络设计中的重点和难点。网络中实现路由发的硬件设备称为路由器。为了使数据包最快的到达目的地,路由器需要选择最优的路径转发数据包。例如在常用的路由算法OSPF(开放式最短路径优先)中,路由器会使用经典的Dijkstra算法计算最短路径,然后尽量沿最短路径转发数据包。现在,若已知一个计算机网络中各路由器间的连接情况,以及各个路由器的最大吞吐量(即每秒能转发的数据包数量),假设所有数据包一定沿最短路径转发,试计算从路由器1到路由器n的网络的最大吞吐量。计算中忽略转发及传输的时间开销,不考虑链路的带宽限制,即认为数据包可以瞬间通过网络。路由器1到路由器n作为起点和终点,自身的吞吐量不用考虑,网络上也不存在将1和n直接相连的链路。

看过印象最深的一句话:网络流从源点到汇点的一条流代表了一条合法路径。对于本题,我们可以考虑先建出原图的最小路径图,(注意,建最小路径图的时候,可以考虑把每条边看作两条有向边,是否在某一条最短路上,即为判断一个端点到起点+边长+另一个端点到终点的距离是否最短路长,如果满足,则它在路径上。而不能通过判断点是否在最短路上,连接两个在最短路上的点边)。

接着又因为每个点有容量,就很容易想到把每个点i 拆开作为两个点i 和 i',两个之间连一条容量为点权的边。接着对于每条最短路径图上的边,从i'->j连一条容量为inf的边即可。

(500跑最短路 floyd多好,为什么要用spfa和dijikstra)

下附AC还是bzoj最慢的代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 2005
#define inf (1e18)
using namespace std;
typedef long long ll;
ll n,m,tot=1;
ll u[maxn*maxn],v[maxn*maxn];
ll w[maxn*maxn];
ll dis[maxn][maxn];
ll head[maxn*maxn],nex[maxn*maxn],to[maxn*maxn];
ll cap[maxn*maxn];
// 把每个点拆乘i i+n
void add(ll x,ll y,ll z)
{to[++tot]=y; cap[tot]=z; nex[tot]=head[x]; head[x]=tot;to[++tot]=x; cap[tot]=z; nex[tot]=head[y]; head[y]=tot;
}
ll iter[maxn*maxn],level[maxn*maxn],q[maxn*maxn];
void bfs(ll now)
{for(ll i=1;i<=n;i++)level[i]=0;ll h=0,t=-1; q[++t]=now; level[now]=1;while(h<=t){now=q[h]; h++;
//      cout<<now<<endl;for(ll i=head[now];i;i=nex[i]){if(!level[to[i]] && cap[i]>0){level[to[i]]=level[now]+1;q[++t]=to[i];}}}
}
ll dfs(ll x,ll y,ll f)
{if(x==y || !f) return f;for(ll &i=iter[x];i;i=nex[i]){if(cap[i]>0 && level[to[i]]==level[x]+1){ll res=dfs(to[i],y,min(f,cap[i]));if(res>0){cap[i]-=res;cap[i^1]+=res;return res;}}}return 0;
}
ll dinic(ll x,ll y)
{ll flow=0;n+=n;while(1){bfs(x);if(!level[y]) return flow;
//      cerr<<"=1"<<endl;for(ll i=1;i<=n;i++)iter[i]=head[i];ll f;while((f=dfs(x,y,inf))) flow+=f;
//      cerr<<"its "<<endl;
//      cerr<<flow<<endl;}
}
int main()
{scanf("%lld%lld",&n,&m);for(ll i=0;i<=n;i++){
//      dis[i][i]=0;for(ll j=0;j<=n;j++)dis[i][j]=inf;dis[i][i]=0;}
//  cerr<<"this "<<dis[1][1]<<endl;for(ll i=1;i<=m;i++){scanf("%lld%lld%lld",&u[i],&v[i],&w[i]);long long temp=min(dis[v[i]][u[i]],w[i]);//dis[u[i]][v[i]]=dis[v[i]][u[i]]=min(dis[v[i]][u[i]],w[i]);dis[u[i]][v[i]]=dis[v[i]][u[i]]=temp;}
//  cerr<<"gg1 "<<dis[1][1]<<endl;for(ll k=1;k<=n;k++){for(ll i=1;i<=n;i++){for(ll j=1;j<=n;j++)if(i!=j && j!=k && k!=i){dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);}}}
//  cout<<"gg "<<dis[1][1]<<endl;
//  cerr<<endl;
//  for(int i=1;i<=n;i++)
//  cerr<<"its "<<dis[1][i]<<endl;for(ll i=1;i<=m;i++){ll x=u[i],y=v[i];ll z=w[i];if(dis[1][x]>dis[1][y]) swap(x,y);
//      cout<<x<<" "<<y<<" "<<dis[1][x]<<" "<<dis[1][y]<<endl;if(dis[1][x]+z+dis[n][y]==dis[1][n]){
//          cout<<x<<" "<<y<<endl;add(x+n,y,1e9);
//          add(y+n,x,inf);}if(dis[1][y]>dis[1][x]) swap(x,y);if(dis[1][y]+z+dis[n][x]==dis[1][n]){add(y+n,x,1e9);}}for(ll i=1;i<=n;i++){ll x;scanf("%lld",&x);add(i,i+n,x);}
//  cerr<<"its "<<dis[1][n]<<endl;
//  cerr<<tot<<endl;ll ans=dinic(n+1,n);printf("%lld\n",ans);
}

PART II 最小割

bzoj 1565 植物大战僵尸

Description

Plants vs. Zombies(PVZ)是最近十分风靡的一款小游戏。Plants(植物)和Zombies(僵尸)是游戏的主角,其中Plants防守,而Zombies进攻。该款游戏包含多种不同的挑战系列,比如Protect Your Brain、Bowling等等。其中最为经典的,莫过于玩家通过控制Plants来防守Zombies的进攻,或者相反地由玩家通过控制Zombies对Plants发起进攻。现在,我们将要考虑的问题是游戏中Zombies对Plants的进攻,请注意,本题中规则与实际游戏有所不同。游戏中有两种角色,Plants和Zombies,每个Plant有一个攻击位置集合,它可以对这些位置进行保护;而Zombie进攻植物的方式是走到植物所在的位置上并将其吃掉。游戏的地图可以抽象为一个N行M列的矩阵,行从上到下用0到N–1编号,列从左到右用0到M–1编号;在地图的每个位置上都放有一个Plant,为简单起见,我们把位于第r行第c列的植物记为Pr, c。Plants分很多种,有攻击类、防守和经济类等等。为了简单的描述每个Plant,定义Score和Attack如下:Score[Pr, c]Zombie击溃植物Pr, c可获得的能源。若Score[Pr, c]为非负整数,则表示击溃植物Pr, c可获得能源Score[Pr, c],若为负数表示击溃Pr, c需要付出能源 -Score[Pr, c]。Attack[Pr, c]植物Pr, c能够对Zombie进行攻击的位置集合。Zombies必须从地图的右侧进入,且只能沿着水平方向进行移动。Zombies攻击植物的唯一方式就是走到该植物所在的位置并将植物吃掉。因此Zombies的进攻总是从地图的右侧开始。也就是说,对于第r行的进攻,Zombies必须首先攻击Pr, M-1;若需要对Pr, c(0 ≤ c < M-1)攻击,必须将Pr,M-1, Pr, M-2 … Pr, c+1先击溃,并移动到位置(r, c)才可进行攻击。在本题的设定中,Plants的攻击力是无穷大的,一旦Zombie进入某个Plant的攻击位置,该Zombie会被瞬间消灭,而该Zombie没有时间进行任何攻击操作。因此,即便Zombie进入了一个Plant所在的位置,但该位置属于其他植物的攻击位置集合,则Zombie会被瞬间消灭而所在位置的植物则安然无恙(在我们的设定中,Plant的攻击位置不包含自身所在位置,否则你就不可能击溃它了)。Zombies的目标是对Plants的阵地发起进攻并获得最大的能源收入。每一次,你可以选择一个可进攻的植物进行攻击。本题的目标为,制定一套ombies的进攻方案,选择进攻哪些植物以及进攻的顺序,从而获得最大的能源收入。

很显然这个attack是一种依赖关系,a attack b 意味着 要打死b必须要打死a,那么我们通过一个图来显示就是 a到b有一条有向边,那么依赖关系就变成了一个图,要打某个节点需要所有它的入点全部被打过,那么如果依赖关系成了一个环,那么环上以及环连出的边都是不可能被选的,所以我们可以通过拓扑排序,来找到那些可以被打死的节点,由于拓扑排序如果有环就进行不下去了,所以自然的,环上以及环之后的点都不被选了。

这样问题就转化成了,有一个点集,选某些点之前,必须选所有它依赖的点,选的收益有正有负,求最大收益。这就是经典问题最大权闭合子图了,我们将正权点i与s连接,从s 到 i 连一条为正权的边,再将负权点j与t连接,从j 到 t 连一条为负权的绝对值的边,再将所有依赖关系如 i选必须选j,连一条i到j 流量为inf的边即可。 注意在拓扑排序前,我们建的边和这里的是相反的,所以如果有边要添加的话,记得反向。

下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#define maxn 1005
#define inf (1e18)
using namespace std;
typedef long long ll;
vector<int>edge[maxn];
ll n,m,s1,t1,tot=1;
ll w[maxn*maxn];
ll head[maxn*maxn],nex[maxn*maxn],to[maxn*maxn],cap[maxn*maxn];
int pos(int i,int j)
{return (i-1)*(int)m+j;
}
void add(ll x,ll y,ll z)
{to[++tot]=y; cap[tot]=z; nex[tot]=head[x]; head[x]=tot;to[++tot]=x; cap[tot]=0; nex[tot]=head[y]; head[y]=tot;
}
ll iter[maxn*maxn],level[maxn*maxn],q[maxn*maxn],in[maxn];
void bfs(ll now)
{for(ll i=1;i<=n;i++)level[i]=0;ll h=0,t=-1; q[++t]=now; level[now]=1;while(h<=t){now=q[h]; h++;for(ll i=head[now];i;i=nex[i]){if(!level[to[i]] && cap[i]>0){level[to[i]]=level[now]+1;q[++t]=to[i];}}}
}
ll dfs(ll x,ll y,ll f)
{if(x==y || !f) return f;for(ll &i=iter[x];i;i=nex[i]){if(cap[i]>0 && level[to[i]]==level[x]+1){
//          cerr<<x<<" "<<y<<endl;ll res=dfs(to[i],y,min(f,cap[i]));if(res>0){cap[i]-=res;cap[i^1]+=res;return res;}}}return 0;
}
ll dinic(ll x,ll y)
{ll flow=0;n=t1;while(1){bfs(x);if(!level[y]) return flow;for(ll i=1;i<=n;i++)iter[i]=head[i];ll f;while((f=dfs(x,y,inf))) flow+=f;
//      cerr<<flow<<endl;}
}
int deg[maxn];
int main()
{scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++){int num;for(int j=1;j<=m;j++){   scanf("%lld%d",&w[pos(i,j)],&num);for(int k=1;k<=num;k++){int x,y;scanf("%d%d",&x,&y);x++; y++;edge[pos(i,j)].push_back(pos(x,y));deg[pos(x,y)]++;
//              add(pos(i,j),pos(x,y),)}}}
//  cerr<<"+1"<<endl;for(int i=1;i<=n;i++){for(int j=1;j<=m-1;j++){edge[pos(i,j+1)].push_back(pos(i,j));deg[pos(i,j)]++;}}int h=0,t=-1;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!deg[pos(i,j)])q[++t]=pos(i,j);while(h<=t){int now=q[h]; h++;
//      cout<<now<<endl;for(int j=0;j<edge[now].size();j++){int nex=edge[now][j];deg[nex]--;if(!deg[nex])q[++t]=nex;}}ll ans=0; s1=n*m+1; t1=n*m+2;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!deg[pos(i,j)]){
//              cerr<<pos(i,j)<<endl;if(w[pos(i,j)]>=0){ans+=w[pos(i,j)];add(s1,pos(i,j),w[pos(i,j)]);}else{add(pos(i,j),t1,-w[pos(i,j)]);}for(int k=0;k<edge[pos(i,j)].size();k++){int nex=edge[pos(i,j)][k];if(!deg[nex]){add(nex,pos(i,j),inf);}}}
//  cerr<<ans<<endl;ll temp=dinic(s1,t1);
//  cerr<<temp<<endl;printf("%lld\n",max(0ll,ans-temp));
}

PART III  费用流

例一:bzoj2673

Description

有一个芯片,芯片上有N*N(1≤N≤40)个插槽,可以在里面装零件。

有些插槽不能装零件,有些插槽必须装零件,剩下的插槽随意。

要求装好之后满足如下两条要求:

1、第 i 行和第 i 列的零件数目必须一样多(1≤i≤N)。

2、第 i 行的零件数目不能超过总的零件数目的 A/B(1≤i≤N,0≤A≤B≤1000,B≠0)。

求最多可以另外放多少个零件(就是除掉必须放的)。如果无解输出impossible。

这道题真是妙啊。(注意是要求最大费用最大流,spfa的时候要改一下哦)

首先我们并不知道这一行最大值是多少。但是我会枚举!我们可以从0-n枚举这一行填的最大值是x,如果我们放出来最多的零件设为b ,它a/b的比例都不满足小于等于A/B,那其他情况b更小,就更不可能满足了。如果在图上体现一行最多放x个呢,那么就是对于每个行i,从s向它连一条边,容量为x,对于每个列j,向t连一条边,容量也为x。

接着如果一个点可以放芯片,那么就从i到j连一条代价为1,流量为1的边,如果这个点本来就是芯片,我们有一个骚操作,将它的代价设置为10000+1,因为10000>n*n,而且我们要求的是最大费用最大流,所以这个是肯定要选的,最后结果ans出来我们用ans/10000就知道了这种必须选的选了多少个,如果不够本来就有芯片点的个数,则说明该状态非法了。

最后就是很妙的如何处理第i行和第j列的个数要求相等,我们只需要从第i行向第i列自己,连一条容量为inf,代价为0的边即可。这时与源点汇点连接的边都满流了。 设当前枚举的最大值为x,那么如果行i,向除了第i列以外的点,放了y个芯片,那么从源点到i的流量还剩x-y,那么这个流只能从我们开始连的第i行向第i列连的边走过去,使得第i列到汇点的边有了x-y的流量,可是这样的话,第i列向源点的连边就会缺y的流量才能满流,这样就需要其他行向第i列的点来向他贡献y个流量,即要求放y个芯片到第i列。

下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 205
using namespace std;
int n,m,s,t,tot=1,ans,sum,a,b;
char s1[maxn][maxn];
int head[maxn*maxn],nex[maxn*maxn],to[maxn*maxn],val[maxn*maxn],cap[maxn*maxn];
void add(int x,int y,int o,int z)
{to[++tot]=y; val[tot]=z;  cap[tot]=o; nex[tot]=head[x]; head[x]=tot; to[++tot]=x; val[tot]=-z; cap[tot]=0; nex[tot]=head[y]; head[y]=tot;
}
int dis[maxn*maxn],minn[maxn*maxn],vis[maxn*maxn],pre[maxn*maxn],q[maxn*maxn];
bool ek()
{for(int i=1;i<=t;i++)dis[i]=-1023456789,vis[i]=0,pre[i]=0,minn[i]=0;int h1=0,t1=-1;minn[s]=1023456789; minn[t]=0; dis[s]=0;  q[++t1]=s; vis[s]=1;while(h1<=t1){int now=q[h1]; h1++; vis[now]=0;for(int i=head[now];i;i=nex[i]){if(cap[i] && dis[to[i]]<dis[now]+val[i]){dis[to[i]]=dis[now]+val[i];minn[to[i]]=min(minn[now],cap[i]);pre[to[i]]=i;if(!vis[to[i]]){vis[to[i]]=1;q[++t1]=to[i];}}}}if(!minn[t]) return false;ans+=minn[t]*dis[t];for(int i=pre[t];i;i=pre[to[i^1]])cap[i]-=minn[t], cap[i^1]+=minn[t];return true;
}
int main()
{int kase=0;while(~scanf("%d%d%d",&n,&a,&b) && (n+a+b)){for(int i=1;i<=n;i++)scanf("%s",s1[i]+1);int cnt=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(s1[i][j]=='C')cnt++;s=n+n+1; t=s+1;int res=-1;for(int maxx=0;maxx<=n;maxx++){ans=0;tot=1;for(int i=0;i<=t;i++) head[i]=0;for(int i=1;i<=n;i++)add(s,i,maxx,0),add(i+n,t,maxx,0),add(i,i+n,123456789,0);for(int i=1;i<=n;i++){for(int j=1;j<=n;j++)if(s1[i][j]!='/'){add(i,j+n,1,(s1[i][j]=='C')?10001:1);}}while(ek());if(ans/10000!=cnt) continue;
//          cerr<<maxx<<" "<<ans%10000<<" "<<ans/10000<<endl;if(a*(ans%10000)/b>=maxx)res=max(res,ans%10000-cnt);}if(res==-1)printf("Case %d: impossible\n",++kase);elseprintf("Case %d: %d\n",++kase,res);}
}

例二 bzoj2668

Description

有一个nm列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第i行第j列的格子只能参与mi,j次交换。

我们可以把问题转化成我们可以移动黑子,每个点有限制被交换过多少次,求最小总移动总次数。

由于移入和移出次数可能是不等的,所以我们可以把一个点拆成三个i,i',i'',那么i到i'的容量即为能够移入的次数,i'-i''的容量为能够移出的次数。

所以我们将所有初始为黑色的点,让源点向它连容量为1,花费为0的边, 将所有终止为黑色的点,让它向源点连一条容量为1,花费为0的边,一个点移动到一个格子,一定要再移动出去(除了在这个点终止),所以,移入和移出的容量均为m[i][j]/2,如果开始是白色,最后是黑色,我移入之后就不用移出了,所以可以比移出量多一次,但是总量不能超过,即为移入量可以为m[i][j]-m[i][j]/2。如果开始是黑色,最后是白色,移出之后就不用移入了,则移除量可以为m[i][j]-m[i][j]/2。如果同色则移入移出量一样了。上述的花费均为0。

最后再考虑移动,就是对于每个点的八个方向连边,容量为无穷,花费为1即可。

最后求一个费用流就好啦。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 1005
using namespace std;
int n,m,s,t,tot=1,ans,sum;
char s1[maxn][maxn],s2[maxn][maxn],s3[maxn][maxn];
int head[maxn*maxn],nex[maxn*maxn],to[maxn*maxn],val[maxn*maxn],cap[maxn*maxn];
int fx[20]={0,0,0,1,1,1,-1,-1,-1};
int fy[20]={0,1,-1,1,0,-1,1,0,-1};
int pos(int i,int j)
{return (i-1)*m+j;
}
void add(int x,int y,int o,int z)
{to[++tot]=y; val[tot]=z;  cap[tot]=o; nex[tot]=head[x]; head[x]=tot; to[++tot]=x; val[tot]=-z; cap[tot]=0; nex[tot]=head[y]; head[y]=tot;
}
int dis[maxn*maxn],minn[maxn*maxn],vis[maxn*maxn],pre[maxn*maxn],q[maxn*maxn];
bool ek()
{for(int i=1;i<=t;i++)dis[i]=1023456789,vis[i]=0,pre[i]=0,minn[i]=0;int h1=0,t1=-1;minn[s]=1023456789; minn[t]=0; dis[s]=0;  q[++t1]=s; vis[s]=1;while(h1<=t1){int now=q[h1]; h1++; vis[now]=0;for(int i=head[now];i;i=nex[i]){if(cap[i] && dis[to[i]]>dis[now]+val[i]){dis[to[i]]=dis[now]+val[i];minn[to[i]]=min(minn[now],cap[i]);pre[to[i]]=i;if(!vis[to[i]]){vis[to[i]]=1;q[++t1]=to[i];}}}}if(!minn[t]) return false;ans+=minn[t]*dis[t];for(int i=pre[t];i;i=pre[to[i^1]])cap[i]-=minn[t], cap[i^1]+=minn[t];return true;
}
void build()
{for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(s1[i][j]=='0' && s2[i][j]=='1'){sum++;add(s,pos(i,j)+n*m,1,0);add(pos(i,j),n*m+pos(i,j),(s3[i][j]-'0')/2,0);add(n*m+(pos(i,j)),n*m*2+pos(i,j),(s3[i][j]-'0'+1)/2,0);}else if(s1[i][j]=='1' && s2[i][j]=='0'){sum--;add(pos(i,j)+n*m,t,1,0);add(pos(i,j),n*m+pos(i,j),(s3[i][j]-'0'+1)/2,0);add(n*m+pos(i,j),n*m*2+pos(i,j),(s3[i][j]-'0')/2,0);}else{add(pos(i,j),n*m+pos(i,j),(s3[i][j]-'0')/2,0);add(n*m+(pos(i,j)),n*m*2+pos(i,j),(s3[i][j]-'0')/2,0);}}}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){for(int k=1;k<=8;k++){int nx=i+fx[k],ny=j+fy[k];if(1<=nx && nx<=n && 1<=ny && ny<=m){add(pos(i,j)+n*m*2,pos(nx,ny),1023456789,1);}}}}
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%s",s1[i]+1);for(int i=1;i<=n;i++)scanf("%s",s2[i]+1);for(int i=1;i<=n;i++)scanf("%s",s3[i]+1);s=n*m*3+1; t=s+1;build();if(sum){printf("-1\n");return 0;}while(ek());printf("%d\n",ans);
}

PARTIV 有上下界的网络流

例一 bzoj 3698

Description

XWW是个影响力很大的人,他有很多的追随者。这些追随者都想要加入XWW教成为XWW的教徒。但是这并不容易,需要通过XWW的考核。
XWW给你出了这么一个难题:XWW给你一个N*N的正实数矩阵A,满足XWW性。
称一个N*N的矩阵满足XWW性当且仅当:(1)A[N][N]=0;(2)矩阵中每行的最后一个元素等于该行前N-1个数的和;(3)矩阵中每列的最后一个元素等于该列前N-1个数的和。
现在你要给A中的数进行取整操作(可以是上取整或者下取整),使得最后的A矩阵仍然满足XWW性。同时XWW还要求A中的元素之和尽量大。

先是有一个结论吧,在上下界网络流的时候,如果对于一个点 i ,有x的流量从s流向它,有y的流量从它流向t,那么如果x-y>0,那么我们就只需要从 s 到 i 连一条容量为x-y的边, 繁殖我们只需要从 i 到 t 连一条 容量为 y-x 的边,正确性显然,也可以推广到其他网络流的建图上。还有就是,对于有上下界的网络流,假如有一条i->j的边我们建边的时候的方法就是,从s向j连一条流量为下界的边,从i到t的时候连一条流量为下界的边,i->j连一条为流量上界-流量下界的边即可。 注意,这里的s是要新建一个起点,t也是要新建一个汇点的,而不能用朴素建图时候的s,t。

还是行与列分开处理,我们对于每一行,从s1(这里的s1 是我们正常建图的s1,并不是上面所说新开的s2,这个s2就是在处理流量差的时候用的)进入它的流量最小值就是这一行的sum即a[i][n]向下取整,最大值就是a[i][n]向上取整。对于每一列,它流出t的流量与行也同理。 接着考虑行与列的关系,第i行连第j列的边也与上面同理考虑,即最小值为a[i][j]向下取整,最大值为a[i][j]向上取整。(由于这是带上下界的网络流,所以我们需要从t1->s1连一条容量为inf的边,来方便下面的操作)

对于有上下界的网络流,我们建刚刚的 s2,t2,就是要先跑 s2->t2的最大流,如果等于下界之和,就说明有合法解,接着再跑s1->t1的最大流,这样跑出来的最大流即为题目要求的最大值了,由于每个元素会在每一列末以及每一行末尾统计一次,所以我们要将答案*3。

下附AC代码。


#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 405
#define inf (1e18)
using namespace std;
typedef long long ll;
ll n,m,s1,t1,s2,t2,tot=1;
double a[maxn][maxn];
ll head[maxn*maxn],nex[maxn*maxn],to[maxn*maxn];
ll cap[maxn*maxn];
void add(ll x,ll y,ll z)
{to[++tot]=y; cap[tot]=z; nex[tot]=head[x]; head[x]=tot;to[++tot]=x; cap[tot]=0; nex[tot]=head[y]; head[y]=tot;
}
ll iter[maxn*maxn],level[maxn*maxn],q[maxn*maxn],in[maxn];
void bfs(ll now)
{for(ll i=1;i<=n;i++)level[i]=0;ll h=0,t=-1; q[++t]=now; level[now]=1;while(h<=t){now=q[h]; h++;for(ll i=head[now];i;i=nex[i]){if(!level[to[i]] && cap[i]>0){level[to[i]]=level[now]+1;q[++t]=to[i];}}}
}
ll dfs(ll x,ll y,ll f)
{if(x==y || !f) return f;for(ll &i=iter[x];i;i=nex[i]){if(cap[i]>0 && level[to[i]]==level[x]+1){
//          cout<<x<<" "<<to[i]<<" "<<cap[i]<<endl;ll res=dfs(to[i],y,min(f,cap[i]));
//          cerr<<x<<" "<<to[i]<<" "<<cap[i]<<" "<<res<<endl;if(res>0){cap[i]-=res;cap[i^1]+=res;return res;}}}return 0;
}
ll dinic(ll x,ll y)
{
//  cerr<<"its "<<x<<" "<<y<<endl;ll flow=0;while(1){bfs(x);if(!level[y]) return flow;for(ll i=1;i<=n;i++)iter[i]=head[i];ll f;while((f=dfs(x,y,inf))) flow+=f;
//      cerr<<flow<<endl;}
}
ll sum=0;
void build()
{for(int i=1;i<n;i++){if(a[i][n]!=(int)(a[i][n])){add(s1,i,1);}in[s1]-=(int)(a[i][n]); in[i]+=(int)(a[i][n]);}for(int i=1;i<n;i++){if(a[n][i]!=(int)(a[n][i])){add(i+n,t1,1);}in[i+n]-=((int)(a[n][i])); in[t1]+=(int)(a[n][i]);}for(int i=1;i<n;i++){for(int j=1;j<n;j++){if((a[i][j]!=(int)(a[i][j]))){add(i,j+n,1);}in[i]-=(int)(a[i][j]); in[j+n]+=(int)(a[i][j]);}}for(int i=1;i<=t2;i++){if(in[i]>0)sum+=in[i],add(s2,i,in[i]);elseadd(i,t2,-in[i]);}add(t1,s1,inf);
}
int main()
{scanf("%lld",&n); s1=2*n+1; t1=s1+1; s2=t1+1; t2=s2+1;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%lf",&a[i][j]);build();
//  cerr<<"+1"<<endl;n=t2;if(dinic(s2,t2)!=sum){printf("No\n");return 0;}printf("%lld\n",dinic(s1,t1)*3ll);
}

例二:bzoj3876

Description

【故事背景】
宅男JYY非常喜欢玩RPG游戏,比如仙剑,轩辕剑等等。不过JYY喜欢的并不是战斗场景,而是类似电视剧一般的充满恩怨情仇的剧情。这些游戏往往
都有很多的支线剧情,现在JYY想花费最少的时间看完所有的支线剧情。
【问题描述】
JYY现在所玩的RPG游戏中,一共有N个剧情点,由1到N编号,第i个剧情点可以根据JYY的不同的选择,而经过不同的支线剧情,前往Ki种不同的新的剧情点。当然如果为0,则说明i号剧情点是游戏的一个结局了。
JYY观看一个支线剧情需要一定的时间。JYY一开始处在1号剧情点,也就是游戏的开始。显然任何一个剧情点都是从1号剧情点可达的。此外,随着游戏的进行,剧情是不可逆的。所以游戏保证从任意剧情点出发,都不能再回到这个剧情点。由于JYY过度使用修改器,导致游戏的“存档”和“读档”功能损坏了,
所以JYY要想回到之前的剧情点,唯一的方法就是退出当前游戏,并开始新的游戏,也就是回到1号剧情点。JYY可以在任何时刻退出游戏并重新开始。不断开始新的游戏重复观看已经看过的剧情是很痛苦,JYY希望花费最少的时间,看完所有不同的支线剧情。

第一题放一道难一点的题这道题就显得比较简单了,这道题就是每个边必须走一次,而且我们可以从每一个点随时回到源点,还有就是走每个边有一定的费用。所以就变成了找一条可行流,使得费用最小。所以直接按上面说的方法建图,因为对于这道题来说肯定存在可行流,所以我们只需要按上面说的方法把图建出来跑费用流即可啦。

下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 200005
using namespace std;
int n,s,t,tot=1,ans;
int in[maxn],out[maxn];
int head[maxn],nex[maxn],to[maxn],val[maxn],cap[maxn];
void add(int x,int y,int o,int z)
{to[++tot]=y; val[tot]=z;  cap[tot]=o; nex[tot]=head[x]; head[x]=tot; to[++tot]=x; val[tot]=-z; cap[tot]=0; nex[tot]=head[y]; head[y]=tot;
}
int dis[maxn],dp[maxn],vis[maxn],pre[maxn],q[maxn];
bool ek()
{for(int i=s;i<=t;i++)dis[i]=1023456789,vis[i]=0,pre[i]=0,dp[i]=0;int h=0,tail=-1;dp[s]=1023456789; dp[tail]=0; dis[s]=0;  q[++tail]=s; vis[s]=1;while(h<=tail){int now=q[h]; h++; vis[now]=0;
//      cerr<<"its "<<h<<endl;
//      cerr<<"its "<<dis[now]<<endl;for(int i=head[now];i;i=nex[i]){if(cap[i] && dis[to[i]]>dis[now]+val[i]){dis[to[i]]=dis[now]+val[i];dp[to[i]]=min(dp[now],cap[i]);pre[to[i]]=i;if(!vis[to[i]]){vis[to[i]]=1;q[++tail]=to[i];}}}}if(!dp[t]) return false;
//  for(int i=s;i<=t;i++)
//      cout<<dp[i]<<' '<<dis[i]<<" "<<pre[i]<<endl;
//  cerr<<dp[t]<<" "<<dis[t]<<endl;ans+=dp[t]*dis[t];for(int i=pre[t];i;i=pre[to[i^1]]){
//      cerr<<to[i^1]<<endl;cap[i]-=dp[t]; cap[i^1]+=dp[t];}
//  cerr<<endl;return true;
}
int main()
{scanf("%d",&n);s=0; t=n+1;for(int i=1;i<=n;i++){int x;scanf("%d",&x); out[i]=x;for(int j=1;j<=x;j++){int y,z;scanf("%d%d",&y,&z);in[y]++;add(i,y,12345678,z);
//          add(s,y,1,z);ans+=z;}
//      add(i,t,x,0);if(i!=1)add(i,1,12345678,0);}for(int i=2;i<=n;i++){if(in[i]>out[i])add(s,i,in[i]-out[i],0);elseadd(i,t,out[i]-in[i],0);}
//  cerr<<"+1"<<endl;while(ek());printf("%d\n",ans);
}

Especially For U 

By ZRX

ZRX的网络流题目总结相关推荐

  1. 网络流题目详讲+题单(提高版)(持续更新中......)

    网络流题目详讲+题单(提高版)(持续更新中......) 标签:图论--网络流 PS:如果你觉得自己还不够强(和我一样弱),可以去入门版看看 阅读体验:https://zybuluo.com/Junl ...

  2. 经典网络流题目模板(P3376 + P2756 + P3381 : 最大流 + 二分图匹配 + 最小费用最大流)...

    题目来源 P3376 [模板]网络最大流 P2756 飞行员配对方案问题 P3381 [模板]最小费用最大流 最大流 最大流问题是网络流的经典类型之一,用处广泛,个人认为网络流问题最具特点的操作就是建 ...

  3. 网络流—Edmonds-Karp 最短增广路算法(最大流)

    网络流----Edmonds-Karp 最短增广路算法 ■求最大流的过程,就是不断找到一条源到汇的路径,然后构建残余网络,再在残余网络上寻找新的路径,使总流量增加,然后形成新的残余网络,再寻找新路径- ...

  4. [BZOJ2502]清理雪道 有上下界网络流(最小流)

    2502: 清理雪道 Time Limit: 10 Sec  Memory Limit: 128 MB Description 滑雪场坐落在FJ省西北部的若干座山上. 从空中鸟瞰,滑雪场可以看作一个有 ...

  5. BZOJ-3876-支线剧情-Ahoi2014-上下界网络流

    描述 [故事背景] 宅男JYY非常喜欢玩RPG游戏,比如仙剑,轩辕剑等等.不过JYY喜欢的并不是战斗场景,而是类似电视剧一般的充满恩怨情仇的剧情.这些游戏往往 都有很多的支线剧情,现在JYY想花费最少 ...

  6. 网络流 小结(更新时间2015/8/8)更新中

    国家队集训队论文-网络流(下载链接) 基础知识我就不再累述了,大家百度百科或找某大牛博客看看就好了 下面是摘自某牛(http://www.cnblogs.com/neverforget/archive ...

  7. 支线剧情-上下界网络流

    支线剧情-上下界网络流 题目描述 题解 有源汇有上下界最小费用可行流 答案即为:新图中求出的费用+++原图中边的下界∗*∗边的费用 上下界网络流推荐博客: 1,https://blog.csdn.ne ...

  8. 网络流(Flow Network)

    更多文章可以在本人的个人小站:https://kaiserwilheim.github.io 查看. 转载请注明出处. (3-13至3-14重修) (6-23至6-24增添上下界网络流相关内容) 什么 ...

  9. 算法学习笔记:网络流#4——ISAP 求解最大流

    算法学习笔记:网络流#4--ISAP 求解最大流 1. 前言 2. 模板 2.1 详解 2.2 正确性证明 2.3 代码 3. 算法对比 3.1 一般数据下的对比 3.2 特殊数据下的对比 4. 总结 ...

最新文章

  1. 盘点深度学习一年来在文本、语音和视觉等方向的进展,看强化学习如何无往而不利
  2. CSS中margin和padding的区别
  3. 你和女朋友的婚后老年生活!
  4. 吴恩达机器学习笔记十四之大规模机器学习
  5. ESXI 6.7安装并部署主机
  6. 开源 微软 语音识别_能用嘴,绝不动手!支持跨屏的语音输入法,它来了!
  7. clickhouse聚合函数之groupBitmap
  8. PAT 乙级 1048 数字加密 (20 分)
  9. arm开发板挂载win10和ubuntu haneWIN NFS Server
  10. u大师装iso系统linux,u大师给苹果电脑装win7win10系统
  11. 地籍测量类毕业论文文献有哪些?
  12. 关于前端直播(videoJS与百度云web播放器:Cyberplayer3.0试用)
  13. System.getProperty()方法大全
  14. window下xmind-pro-8破解版
  15. Error排错:container runtime network not ready
  16. Joplin 软件转换中文
  17. 点集配准技术(ICP、RPM、KC、CPD)
  18. win7驱动程序未经签名可以使用吗_windows-7 – Windows7引导选项,允许忽略未签名的驱动程序...
  19. 华为云618年中钜惠,服务器免费领三个月
  20. 快递扫地机器人被损坏_熬夜秒到的扫地机器人丢了 快递公司最多赔几十元

热门文章

  1. 【NDN基础】Named Data Networking 学习笔记
  2. 元宇宙:从数字孪生到数字共生
  3. bash:bison未找到命令
  4. FPGA的复位设计要醒目点啦
  5. [评估指标] 敏感性/特异性/PPV/NPV等指标原理与计算方法
  6. 联想R7000P莫名其妙黑屏问题记录
  7. @Transactional注解用法
  8. 解决json数据 key为数字,自动排序问题
  9. 计算机组成--PC和IP的区别
  10. js 中文汉字按拼音排序,浏览器英文环境也可正确运行