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
很显然这个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);
else
printf("Case %d: %d\n",++kase,res);
}
}
例二 bzoj2668
Description
我们可以把问题转化成我们可以移动黑子,每个点有限制被交换过多少次,求最小总移动总次数。
由于移入和移出次数可能是不等的,所以我们可以把一个点拆成三个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]);
else
add(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
第一题放一道难一点的题这道题就显得比较简单了,这道题就是每个边必须走一次,而且我们可以从每一个点随时回到源点,还有就是走每个边有一定的费用。所以就变成了找一条可行流,使得费用最小。所以直接按上面说的方法建图,因为对于这道题来说肯定存在可行流,所以我们只需要按上面说的方法把图建出来跑费用流即可啦。
下附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);
else
add(i,t,out[i]-in[i],0);
}
// cerr<<"+1"<<endl;
while(ek());
printf("%d\n",ans);
}
Especially For U
By ZRX