注:因为风骨傲天习惯用(Dfs+Dinic),所以不会用到(EK)等其他形式,而预流推进等较高级的,等我学了再说吧……
一、基础知识
事实上,这一部分只会包括最大流和最小费用最大流的略解,后面会补上带上下界的。
- 最大流:(Bfs+Dfs)不多说,反正不难。
- 最小费用最大流:最短路(+Dfs),和上面差不多,只是改一下(Bfs)即可。
- 当前弧优化:用(cur)记一下上一次跑哪了即可(上代码,东西都在代码里)。
最大流:
inline bool bfs()
{
memset(d,0,sizeof(d));d[s]=1;
queue<int> q;q.push(s);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=head[x];i!=-1;i=edge[i].next)
if(!d[edge[i].to]&&edge[i].dis)
d[edge[i].to]=d[x]+1,q.push(edge[i].to);
}
return d[t]?1:0;
}
inline int dfs(int pos,int dis)
{
if(pos==t) return dis;
for(int i=cur[pos];i!=-1;i=edge[i].next)
{
cur[pos]=i;
if(d[edge[i].to]==d[pos]+1&&edge[i].dis>0)
{
int data=dfs(edge[i].to,min(edge[i].dis,dis));
if(data>0)
{
edge[i].dis-=data;
edge[i^1].dis+=data;
return data;
}
}
}
return 0;
}
inline int Dinic()
{
int ans=0;
while(bfs())
{
memcpy(cur,head,sizeof(cur));
while(int data=dfs(s,0x3f3f3f3f))
ans+=data;
}
return ans;
}
费用流:
inline bool bfs()
{
for(int i=1;i<=n;i++) d[i]=-0x3f3f3f3f;
memset(v,0,sizeof(v));memset(flo,0,sizeof(flo));
queue<int> q;q.push(s);d[s]=0;v[s]=1;flo[s]=1;
while(!q.empty())
{
int x=q.front(); q.pop(); v[x]=0;
for(int i=head[x];i!=-1;i=edge[i].next)
{
int y=edge[i].to;
if(edge[i].dis>0&&d[y]<d[x]+edge[i].cos)
{
d[y]=d[x]+edge[i].cos;
flo[y]=flo[x]+1;
if(!v[y]){q.push(y);v[y]=1;}
}
}
}
if(d[t]==-0x3f3f3f3f) return 0;
else return 1;
}
int dfs(int pos,int dis)
{
if(pos==t) return dis;
for(int i=cur[pos];i!=-1;i=edge[i].next)
{
int y=edge[i].to;cur[pos]=i;
if(flo[y]==flo[pos]+1&&edge[i].dis!=0&&d[y]==d[pos]+edge[i].cos)
{
int data=dfs(y,min(dis,edge[i].dis));
if(data>0)
{
edge[i].dis-=data;
edge[i^1].dis+=data;
ans_cos+=edge[i].cos*data;
return data;
}
}
}
return 0;
}
int dinic()
{
int ans=0;
while(bfs())
{
memcpy(cur,head,sizeof(head));
while(int data=dfs(s,0x3f3f3f3f))
ans+=data;
}
return ans;
}
二、最小割
实际上,最小割的应用很多:详见国家集训队胡伯涛论文:最小割模型在信息学中的应用
1.最大权闭合子图
闭合图:对于一个有向图(G),存在点集合(V),任取点(u)属于(V),(u)的出边的另一个点也属于(V),则为闭合图。
最大闭合子图,即原图每个点有点权,求原图的闭合子图中最大点权和(点权可正可负)。
转化:(S)向所有正权点建边,容量为点权,所有负权点向(T)建边,容量为点权绝对值,原边容量为(Inf)。
最大权(=)正权和(-)最大流。一个通俗易懂的证明
变式:([CEOI2008]order)(下附代码):将原边容量改为租金即可(因为租用与否仅和此工序有关)
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int f=1,w=0;char x=0;
while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
return w*f;
}
const int N=300010;
int n,m,sum,s,t;
int head[N],num_edge,d[N],cur[N];
struct {int next,to,dis;} edge[N*10];
inline void add(int from,int to,int dis)
{
edge[++num_edge].next=head[from];
edge[num_edge].dis=dis;
edge[num_edge].to=to;
head[from]=num_edge;
}
inline bool bfs()
{
memset(d,0,sizeof(d));d[s]=1;
queue<int> q;q.push(s);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=head[x];i!=-1;i=edge[i].next)
if(!d[edge[i].to]&&edge[i].dis)
d[edge[i].to]=d[x]+1,q.push(edge[i].to);
}
return d[t]?1:0;
}
inline int dfs(int pos,int dis)
{
if(pos==t) return dis;
for(int i=cur[pos];i!=-1;i=edge[i].next)
{
cur[pos]=i;
if(d[edge[i].to]==d[pos]+1&&edge[i].dis>0)
{
int data=dfs(edge[i].to,min(edge[i].dis,dis));
if(data>0)
{
edge[i].dis-=data;
edge[i^1].dis+=data;
return data;
}freopen("A.in","r",stdin);//12
}
}
return 0;
}
inline int Dinic()
{
int ans=0;
while(bfs())
{
memcpy(cur,head,sizeof(cur));
while(int data=dfs(s,0x3f3f3f3f))
ans+=data;
}
return ans;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("A.in","r",stdin);
#endif
n=read(),m=read();s=0,t=n+m+2;
num_edge=-1;memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)
{
int w=read(),c=read();sum+=w;
add(0,i,w),add(i,0,0);
for(int j=1;j<=c;j++)
{
int id=read();w=read();
add(i,id+n,w),add(id+n,i,0);
}
}
for(int i=1,w;i<=m;i++)
w=read(),add(i+n,t,w),add(t,i+n,0);
printf("%d",sum-Dinic());
}
三、最小路径覆盖
转化:先拆点为(v,v{'}),原图中(u)和(v)有边,连(u,v{'})即可。最小路径(=)点数(-)最大匹配。
变式:([SDOI2010])星际竞速:考虑跳跃,无论从哪个点跳到(i),花费均为(A[i]),所以可以建(S)到(v{'}),容量为(A[i])的边。(下附代码)
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int f=1,w=0;char x=0;
while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
return w*f;
}
const int N=300010;
int n,m,sum,s,t,flo[N],v[N],ans;
int head[N],num_edge,d[N],cur[N];
struct {int next,to,dis,cos;} edge[N*10];
inline void add(int from,int to,int dis,int cos)
{
edge[++num_edge].next=head[from];
edge[num_edge].dis=dis;
edge[num_edge].cos=cos;
edge[num_edge].to=to;
head[from]=num_edge;
}
inline bool bfs()
{
memset(d,0x3f3f3f3f,sizeof(d));
memset(v,0,sizeof(v));memset(flo,0,sizeof(flo));
queue<int> q;q.push(s);flo[s]=1;v[s]=1;d[s]=0;
while(!q.empty())
{
int x=q.front();q.pop();v[x]=0;
for(int i=head[x];i!=-1;i=edge[i].next)
if(d[edge[i].to]>d[x]+edge[i].cos&&edge[i].dis>0)
{
d[edge[i].to]=d[x]+edge[i].cos;
flo[edge[i].to]=flo[x]+1;
if(!v[edge[i].to]) q.push(edge[i].to),v[edge[i].to]=1;
}
}
return d[t]<0x3f3f3f3f?1:0;
}
inline int dfs(int pos,int dis)
{
if(pos==t) return dis;
for(int i=cur[pos];i!=-1;i=edge[i].next)
{
int y=edge[i].to;cur[pos]=i;
if(d[y]==d[pos]+edge[i].cos&&edge[i].dis!=0&&flo[y]==flo[pos]+1)
{
int data=dfs(y,min(edge[i].dis,dis));
if(data>0)
{
edge[i].dis-=data;
edge[i^1].dis+=data;
ans+=data*edge[i].cos;
return data;
}
}
}
return 0;
}
inline int Dinic()
{
while(bfs())
{
memcpy(cur,head,sizeof(cur));
while(int data=dfs(s,0x3f3f3f3f));
}
return ans;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("A.in","r",stdin);//12
//freopen("B.in","r",stdin);//6
//freopen("C.in","r",stdin);//230
#endif
n=read(),m=read();s=0,t=(n<<1)+1;
num_edge=-1;memset(head,-1,sizeof(head));
for(int i=1,w;i<=n;i++)
w=read(),add(s,i+n,1,w),add(i+n,s,0,-w);
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
if(v<u) swap(u,v);
add(u,v+n,1,w),add(v+n,u,0,-w);
}
for(int i=1;i<=n;i++)
add(s,i,1,0),add(i,s,0,0),add(i+n,t,1,0),add(t,i+n,0,0);
printf("%d",Dinic());
}
四、各种奇特的建图
1、与时间有关:
其实这样的题以前考过一次,好像那次(Itst)是全场唯一一个切那道题的,现在再放一道题:
显然和时间有关,要分时间段建边。
分析性质,对于一个修车工,假设其修的车用时分别为:(T_1,T_2……,T_p),那么总等待时间为:(T_p*1+T_{p-1}*2+……+T_1*p)。
可以看出一个修车工所修的第倒数(k)个车对答案的贡献为(k*T)。所以我们可以将每个修车工拆为(N)个时间段,表示倒数几个修,将每个车和拆开的点建边,费用为时间段编号乘花费时间即可。(下附代码)
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int f=1,w=0;char x=0;
while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
return w*f;
}
const int N=6000,M=100010;
int n,m,num_edge,s,t,ans;
int d[N],head[N],v[N],flo[N],cur[N];
struct Edge{int next,to,dis,cos;} edge[M];
inline void add(int from,int to,int dis,int cos)
{
edge[++num_edge].next=head[from];
edge[num_edge].dis=dis;
edge[num_edge].cos=cos;
edge[num_edge].to=to;
head[from]=num_edge;
}
inline bool bfs()
{
memset(d,0x3f3f3f3f,sizeof(d));
memset(v,0,sizeof(v));memset(flo,0,sizeof(flo));
queue<int> q;q.push(s);d[s]=0,v[s]=flo[s]=1;
while(!q.empty())
{
int x=q.front();q.pop();v[x]=0;
for(int i=head[x];i!=-1;i=edge[i].next)
if(d[edge[i].to]>d[x]+edge[i].cos&&edge[i].dis>0)
{
d[edge[i].to]=d[x]+edge[i].cos;
flo[edge[i].to]=flo[x]+1;
if(!v[edge[i].to]) q.push(edge[i].to),v[edge[i].to]=1;
}
}
return d[t]<0x3f3f3f3f?1:0;
}
inline int dfs(int pos,int dis)
{
if(pos==t) return dis;
for(int i=cur[pos];i!=-1;i=edge[i].next)
{
int y=edge[i].to;cur[pos]=i;
if(flo[y]==flo[pos]+1&&d[y]==d[pos]+edge[i].cos&&edge[i].dis>0)
{
int data=dfs(y,min(dis,edge[i].dis));
if(data>0)
{
edge[i].dis-=data;
edge[i^1].dis+=data;
ans+=edge[i].cos*data;
return data;
}
}
}
return 0;
}
inline int Dinic()
{
while(bfs())
{
memcpy(cur,head,sizeof(head));
while(int data=dfs(s,0x3f3f3f3f));
}
return ans;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("A.in","r",stdin);//Ans=1.50
#endif
num_edge=-1;memset(head,-1,sizeof(head));
m=read(),n=read();s=0,t=m*n+n+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int T=read();
for(int k=1;k<=n;k++)
add(i,n*j+k,1,k*T),add(n*j+k,i,0,-k*T);
}
for(int i=1;i<=n;i++) add(s,i,1,0),add(i,s,0,0);
for(int i=1;i<=n*m;i++) add(i+n,t,1,0),add(t,i+n,0,0);
printf("%.2lf",(double)Dinic()/(double)n);
}
(To) (be) (continued...)