网络流
网络流
没有赘述概念和理解,想了解可以看参考资料和oi-wiki
没什么营养的板子
细节!
Dinic
-
建边
tot=1
-
反边初始容量为零
add(y,x,0)
-
中间任意时刻能流的量为零了,直接跳 如
&&c
、&&res
-
d
数组分层用,如果不能往后面流了,直接封死if(!flow) d[u]=-1
-
弧优化
dinic
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 205,M = 5005;
int n,m,s,t;
int head[N],tot=1;// 初始 tot=0,后面定反边用
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
namespace FLOW
{
int T;
int d[N],now[N];
inline LL dfs(int u,LL res)
{
if(u==T) return res;
LL flow=0;
for(int i=now[u];i&&res;i=e[i].u)//res==0 时没有流量了,直接跳
{
now[u]=i;//弧优化
int v=e[i].v,c=min((LL)e[i].w,res);
if(d[v]==d[u]+1&&c)//c==0 时没流量了,直接跳
{
int k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;//反边加容量,后面可以反悔
}
}
if(!flow) d[u]=-1;//不能继续增广 ,路死了
return flow;
}
inline LL dinic(int s,int t)
{
T=t;//注意
LL flow=0;
while(1)
{
queue<int> q;
memcpy(now,head,sizeof(head));
memset(d,-1,sizeof(d));//分层,初始为零
q.push(s); d[s]=0;
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e18);
}
}
} using namespace FLOW;
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++)
{
int x,y,z; scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,0);//注意,反边初始容量为 0
}
printf("%lld\n",dinic(s,t));
return 0;
}
EK
EK
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1200+5,M = 120000+5;
int n,m,s,t;
int head[N],tot=1;//从一开始
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
LL f[N];
int pre[N];
LL EK(int s,int t)
{
LL flow=0;
while(1)
{
queue<int> q;
memset(f,-1,sizeof(f));
f[s]=1e18; q.push(s);//初始流量极大值
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(e[i].w&&f[v]==-1)
{
f[v]=min(f[u],(LL)e[i].w); pre[v]=i;//记录路径
q.push(v);
}
}
}
if(f[t]==-1) return flow;
flow+=f[t];
for(int u=t;u!=s;u=e[pre[u]^1].v) e[pre[u]].w-=f[t],e[pre[u]^1].w+=f[t];//回溯
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++)
{
int x,y,z; scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,0);
}
printf("%lld\n",EK(s,t));
return 0;
}
SSP
-
spfa 出队删标记
-
反边容量初始为 0,费用为负。
-
边数不是
N<<1
!!!
ssp
#include<bits/stdc++.h>
using namespace std;
#define P pair<int,int>
const int N = 405,M = 15005;
int n,m;
int head[N],tot=1;
struct E {int u,v,w,c;} e[M<<1];//边数为M
inline void add(int u,int v,int w,int c) {e[++tot]={head[u],v,w,c}; head[u]=tot;}
int d[N],f[N],pre[N];
bool vs[N];
P ssp(int s,int t)
{
int flow=0,cost=0;
while(1)
{
queue<int> q;
memset(d,0x3f,sizeof(d));
memset(vs,0,sizeof(vs));
q.push(s); d[s]=0; f[s]=1e9;//初始流量极大值
while(!q.empty())
{
int u=q.front(); q.pop(); vs[u]=0;//出队删标记
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v,dis=d[u]+e[i].c;
if(e[i].w&&dis<d[v])
{
d[v]=dis; pre[v]=i; f[v]=min(f[u],e[i].w); //记录路径
if(!vs[v]) vs[v]=1,q.push(v);
}
}
}
if(d[t]>1e9) return {flow,cost};
flow+=f[t]; cost+=f[t]*d[t];
for(int u=t;u!=s;u=e[pre[u]^1].v) e[pre[u]].w-=f[t],e[pre[u]^1].w+=f[t];//回溯
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y,z,w; scanf("%d%d%d%d",&x,&y,&z,&w);
add(x,y,z,w); add(y,x,0,-w);
}
P ans=ssp(1,n);
printf("%d %d\n",ans.first,ans.second);
return 0;
}
题
开门大吉 (集合划分问题)
做了几道类似的题,感觉关键在于决策的并联、串联和限制。
在不考虑限制的条件下,“串联”的决策需要在一条路径上做出一个选择,而“并联”表示决策具有独立性,每条路径都单独做出决策。
在这种由几条长链连接源点和汇点的图中,显然最小割就是最优决策。
如果再加入限制呢?
也就是规定一些边不能同时被选(或者是同时被选有额外代价),那么我们希望图中得到的效果就是:如果两条边同时被选,那么图仍没有被割成两个不相交的点集(割不动)。
所以我们可以通过在不同的长链间添加“横插边”来提供种限制,至于容量直接设置成无限大,怎么割也割不完,所以不能同时选,并且不会影响其他的条件。
注意加的限制需要考虑方向,有时候需要加双向边,也就是正反都会有代价,实际上就是正反容量初始都不为零,这样怎么 割都可以,就是双向边了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 100005,M = 1000005;
const double del = 1e-18,inf = 1e9;
int n,m,p,TT,q,c[N];
int head[N],tot=1;
struct E {int u,v; double w;} e[M<<1];
inline void add(int u,int v,double w) {e[++tot]={head[u],v,w}; head[u]=tot; /*printf("%d %d %lf\n",u,v,w)*/;}
void init()
{
memset(head,0,sizeof(head)); tot=1;
}
int T,now[N],d[N];
double dfs(int u,double res)
{
if(u==T) return res;
double flow=0;
for(int i=now[u];i&&res>del;i=e[i].u)
{
now[u]=i;
int v=e[i].v; double c=min(res,e[i].w);
if(d[v]==d[u]+1&&c>del)
{
double k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
}
}
if(flow<del) d[u]=-1;
return flow;
}
double dinic(int s,int t)
{
T=t;
double flow=0;
while(1)
{
queue<int> q;
memset(d,-1,sizeof(d));
memcpy(now,head,sizeof(head));
d[s]=0; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(d[v]==-1&&e[i].w>del) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e9);
if(flow>=1e9) return flow;
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d",&TT);
while(TT--)
{
init();
scanf("%d%d%d%d",&n,&m,&p,&q); int s=0,t=n*m+1;
for(int i=1;i<=p;i++) scanf("%d",&c[i]);
for(int j=1;j<=m;j++)
{
for(int i=1;i<=n;i++)
{
double sum=0,tt=1;
for(int k=1;k<=p;k++)
{
double x; scanf("%lf",&x);
tt*=x; sum=sum+tt*c[k];
}
if(j==m) add((i-1)*m+j,t,sum),add(t,(i-1)*m+j,0);
else add((i-1)*m+j,(i-1)*m+j+1,sum),add((i-1)*m+j+1,(i-1)*m+j,0);
}
}
for(int i=1;i<=n;i++)
{
add(s,(i-1)*m+1,inf); add((i-1)*m+1,s,0);
}
for(int i=1;i<=q;i++)
{
int x,y,z; scanf("%d%d%d",&x,&y,&z);
for(int j=1;j<=m;j++)
{
if(j+z<=m&&j+z>=1) add((y-1)*m+j,(x-1)*m+j+z,inf),add((x-1)*m+j+z,(y-1)*m+j,0);
else if(j+z<1) continue;//注意有负数
else add((y-1)*m+j,t,inf),add(t,(y-1)*m+j,0);
}
}
double ans=dinic(s,t);
if(ans>=1e9) printf("-1\n");
else printf("%lf\n",ans);
}
return 0;
}
切糕(最小割离散变量)
集合划分的应用,本质一样。
不知道为什么起了个新名字,板板板,典典典。
发现在每个 \((x,y)\) 的 \(z\) 个决策中选一个,每个 \((x,y)\) 之间互不影响(暂时不考虑额外限制)。
所以每个 \((x,y)\) 的 \(z\) 个决策连一条长链连接源点和汇点,一共 \(xy\) 条链,然后再加入限制,挺典。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 50,inf = 1e9;
int p,q,r,D;
int a[N][N][N],cnt,mp[N][N][N];
int head[N*N*N],tot=1;
struct E {int u,v,w;} e[N*N*N<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot; /* printf("%d %d\n",u,v);*/}
int xx[4]={0,1,-1,0},yy[4]={1,0,0,-1};
int T,now[N*N*N],d[N*N*N];
int dfs(int u,int res)
{
if(u==T) return res;
int flow=0;
for(int i=now[u];i&&res;i=e[i].u)
{
now[u]=i;
int v=e[i].v,c=min(res,e[i].w);
if(d[v]==d[u]+1&&c)
{
int k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
}
}
if(flow==0) d[u]=-1;
return flow;
}
inline int dinic(int s,int t)
{
int flow=0; T=t;
while(1)
{
queue<int> q;
memset(d,-1,sizeof(d));
memcpy(now,head,sizeof(head));
d[s]=0; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(e[i].w&&d[v]==-1) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e9);
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d%d%d",&p,&q,&r,&D);
for(int k=1;k<=r;k++)
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++) scanf("%d",&a[k][i][j]),mp[k][i][j]=++cnt;
int s=0,t=cnt+1;
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++)
{
add(s,mp[1][i][j],inf); add(mp[1][i][j],s,0);
for(int k=1;k<r;k++) add(mp[k][i][j],mp[k+1][i][j],a[k][i][j]),add(mp[k+1][i][j],mp[k][i][j],0);
add(mp[r][i][j],t,a[r][i][j]),add(t,mp[r][i][j],0);
}
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++)
for(int h=0;h<4;h++)
{
int x=i+xx[h],y=j+yy[h];
if(x<1||x>p||y<1||y>q) continue;
for(int k=D+1;k<=r;k++) add(mp[k][i][j],mp[k-D][x][y],inf),add(mp[k-D][x][y],mp[k][i][j],0);
}
printf("%d\n",dinic(s,t));
return 0;
}
货币
题面
挺板的集合划分问题,转化问题就简单了。
把 \((1,i)\) 到 \((2,i)\) 的边也看成特殊限制,也就是如果同时走了 \((1,i-1)\) 和 \((2,i)\),那么会多 \(c_i\) 的代价。
每一个 \(i\) 可以选择在第一行走,也可以选择在第二行走,直接连 \(i\) 条长链,然后加入限制。
在 \(i\) 选了第一行,\(j\) 选了第二行,会多代价,连一条代价为 \(c_i\) 的横插边就好了。注意有一些双向边。
code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 505,M = 3005;
int n,m;
int head[N],tot=1;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
int T,d[N],now[N];
LL dfs(int u,LL res)
{
if(u==T) return res;
LL flow=0;
for(int i=now[u];i&&res;i=e[i].u)
{
now[u]=i;
int v=e[i].v,c=min((LL)e[i].w,res);
if(d[v]==d[u]+1&&c)
{
int k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
}
}
if(!flow) d[u]=-1;
return flow;
}
LL dinic(int s,int t)
{
LL flow=0; T=t;
while(1)
{
queue<int> q;
memset(d,-1,sizeof(d));
memcpy(now,head,sizeof(head));
d[s]=0; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e18);
}
}
int main()
{
freopen("currency.in","r",stdin);
freopen("currency.out","w",stdout);
scanf("%d%d",&n,&m); int s=0,t=n+1;
for(int i=1;i<n;i++)
{
int x; scanf("%d",&x); add(s,i,x); add(i,s,0);
}
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x);
if(i==1) add(i,t,x),add(t,i,0);
else if(i==n) add(s,i-1,x),add(i-1,s,0);
else
{
add(i-1,i,x); add(i,i-1,x);
}
}
for(int i=1;i<n;i++)
{
int x; scanf("%d",&x); add(i,t,x); add(t,i,0);
}
for(int i=1;i<=m;i++)
{
int x,y,z; scanf("%d%d%d",&x,&y,&z);
add(y,x,z); add(x,y,0);
}
printf("%lld\n",dinic(s,t));
return 0;
}
飞行员配对方案问题 (二分图最大匹配)
二分图最大匹配板子,考虑左右部分别连源点和汇点,容量都为一,中间的边容量也是一,,因为一个人只能连一个人。最后方案找满流的边。(注意边数很大)
code
#include<bits/stdc++.h>
using namespace std;
const int N = 105,M = 1e5+5;
int n,m;
int head[N],tot=1;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
namespace FLOW
{
int T,now[N],d[N];
int dfs(int u,int res)
{
if(u==T) return res;
int flow=0;
for(int i=now[u];i&&res;i=e[i].u)
{
now[u]=i;
int v=e[i].v,c=min(res,e[i].w);
if(d[v]==d[u]+1&&c)
{
int k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
}
}
if(!flow) d[u]=-1;
return flow;
}
int dinic(int s,int t)
{
T=t;
int flow=0;
while(1)
{
queue<int> q;
memset(d,-1,sizeof(d));
memcpy(now,head,sizeof(head));
d[s]=0; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e9);
}
}
} using namespace FLOW;
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d",&m,&n); n=n-m;
for(int i=1;i<=m;i++) add(0,i,1),add(i,0,0) ;
for(int i=1;i<=n;i++) add(i+m,n+m+1,1),add(n+m+1,i+m,0);
int x,y;
while(scanf("%d%d",&x,&y)&&x!=-1&&y!=-1)
add(x,y,1),add(y,x,0);
int ans=dinic(0,n+m+1);
if(ans==0)
{
printf("No Solution!");
return 0;
}
printf("%d\n",ans);
for(int u=1;u<=m;u++)
{
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v; if(e[i^1].w==0||e[i].w||v==0||v==n+m+1) continue;
printf("%d %d\n",u,v);
}
}
return 0;
}
方格取数问题(二分图最大(权)独立集)
方格取数是二分图最大权匹配,网络流的做法就是统计总价值,然后用最小割刻画最小的冲突代价,然后相减。
其他的都是二分图最大匹配,就是权值为 \(1\).
具体的建图方式:
将所有点分成两部分,保证每一部分内的点之间没有冲突(二分图),分别用源点和汇点连边,容量为价值,中间冲突用无穷大的容量刻画。
这样是否删减的信息只在与源点和汇点的边上,中间的边只描述冲突,不参与统计(割不动)。
注意第一步要把所有点分成两部分,一般直接黑白交错(横纵坐标之和的奇偶性),可以满足”不相邻“和”日字“限制。
长脖子鹿放置出现了奇葩的”目字“限制,考虑直接按行号奇偶分,就做完了。
P2774
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5,M = 1e5+5,inf = 1e9;
int n,m;
int head[N],tot=1,a[N],mp[105][105],num,ans;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
int xx[4]={0,0,1,-1},yy[4]={1,-1,0,0};
int T,now[N],d[N];
int dfs(int u,int res)
{
if(u==T) return res;
int flow=0;
for(int i=now[u];i&&res;i=e[i].u)
{
now[u]=i;
int v=e[i].v,c=min(e[i].w,res);
if(d[v]==d[u]+1&&c)
{
int k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
}
}
if(!flow) d[u]=-1;
return flow;
}
int dinic(int s,int t)
{
T=t;int flow=0;
while(1)
{
queue<int> q;
memset(d,-1,sizeof(d));
memcpy(now,head,sizeof(head));
d[s]=0; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e9);
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
mp[i][j]=++num;
scanf("%d",&a[num]); ans=ans+a[num];
}
}
int s=0,t=num+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if((i+j)&1)
{
add(s,mp[i][j],a[mp[i][j]]);
add(mp[i][j],s,0);
for(int k=0;k<4;k++)
{
int x=i+xx[k],y=j+yy[k];
if(x<1||x>n||y<1||y>m) continue;
add(mp[i][j],mp[x][y],inf);
add(mp[x][y],mp[i][j],0);
}
}
else
{
add(mp[i][j],t,a[mp[i][j]]);
add(t,mp[i][j],0);
}
}
}
printf("%d\n",ans-dinic(s,t));
return 0;
}
P5030
#include<bits/stdc++.h>
using namespace std;
const int N = 4e4+5,M = 4e5+5,inf = 1e9;
int n,m,S;
int head[N],tot=1,a[N],mp[205][205],num,ans;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
int xx[8]={-3,-1,1,3,3,1,-1,-3},yy[8]={1,3,3,1,-1,-3,-3,-1};
int T,now[N],d[N];
int dfs(int u,int res)
{
if(u==T) return res;
int flow=0;
for(int i=now[u];i&&res;i=e[i].u)
{
now[u]=i;
int v=e[i].v,c=min(e[i].w,res);
if(d[v]==d[u]+1&&c)
{
int k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
}
}
if(!flow) d[u]=-1;
return flow;
}
int dinic(int s,int t)
{
T=t;int flow=0;
while(1)
{
queue<int> q;
memset(d,-1,sizeof(d));
memcpy(now,head,sizeof(head));
d[s]=0; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e9);
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d%d",&n,&m,&S);
for(int i=1;i<=S;i++)
{
int x,y; scanf("%d%d",&x,&y);
mp[x][y]=-1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(mp[i][j]!=-1) mp[i][j]=++num,ans++;
}
}
int s=0,t=num+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) if(mp[i][j]!=-1)
{
if((i)&1)
{
add(s,mp[i][j],1);
add(mp[i][j],s,0);
for(int k=0;k<8;k++)
{
int x=i+xx[k],y=j+yy[k];
if(x<1||x>n||y<1||y>n||mp[x][y]==-1) continue;
add(mp[i][j],mp[x][y],inf);
add(mp[x][y],mp[i][j],0);
}
}
else
{
add(mp[i][j],t,1);
add(t,mp[i][j],0);
}
}
}
printf("%d\n",ans-dinic(s,t));
return 0;
}
圆桌问题(二分图多重匹配)
板,好像是扩展版的最大匹配。转化问题为每组匹配 \(r_i\) 个桌子,然后做完了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1000+5,M = 1e5+5;
int n,m,s,t;
int head[N],tot=1,sum;
struct E {int u,v,w;} e[M<<1];
inline void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}
int T,now[N],d[N];
int dfs(int u,int res)
{
if(u==T) return res;
int flow=0;
for(int i=now[u];i&&res;i=e[i].u)
{
now[u]=i;
int v=e[i].v,c=min(res,e[i].w);
if(d[v]==d[u]+1&&c)
{
int k=dfs(v,c);
flow+=k; res-=k; e[i].w-=k; e[i^1].w+=k;
}
}
if(!flow) d[u]=-1;
return flow;
}
int dinic(int s,int t)
{
int flow=0; T=t;
while(1)
{
queue<int> q;
memset(d,-1,sizeof(d));
memcpy(now,head,sizeof(head));
d[s]=0; q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(d[v]==-1&&e[i].w) d[v]=d[u]+1,q.push(v);
}
}
if(d[t]==-1) return flow;
flow+=dfs(s,1e9);
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d%d",&m,&n); s=0; t=n+m+1;
for(int i=1;i<=m;i++)
for(int j=m+1;j<=m+n;j++)
add(i,j,1), add(j,i,0);
for(int i=1;i<=m;i++)
{
int x; scanf("%d",&x); sum+=x;
add(s,i,x); add(i,s,0);
}
for(int i=m+1;i<=m+n;i++)
{
int x; scanf("%d",&x);
add(i,t,x); add(t,i,0);
}
int ans=dinic(s,t);
if(ans<sum) printf("0\n");
else
{
printf("1\n");
for(int u=1;u<=m;u++)
{
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(e[i].w==0&&e[i^1].w) printf("%d ",v-m);
}
printf("\n");
}
}
return 0;
}
餐巾计划问题 (建模)
- 流量守恒
- 最大流等于最小割
- 拆点
首先发现干净的餐巾不能作为脏的继续流,所以把每天的餐巾分成两部分,干净的和脏的,这样就方便进行转化操作了。
注意每天流进多少干净的餐巾,就会流出多少脏的餐巾。这就恰好对应了网络流的一个重要的性质:流量守恒。
于是,不如直接让源点向每天流出固定的脏餐巾,让汇点收入固定的干净的餐巾(这里其实是一个逆向的过程)。
这样既满足了流量守恒(源点流出的一定等于汇点流入的),又使图的最小割(满流的边)恰好为所需要的餐巾数。
……其实在这种定义之下,可以凭空出现一个和S,T不通的环流。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 4e3+5,M = 4e6+5,inf = 1e9;
int n,s,t;
int head[N],tot=1;
struct E {int u,v,w,c;} e[M<<1];
inline void add(int u,int v,int w,int c) {e[++tot]={head[u],v,w,c}; head[u]=tot;}
int pre[N];
long long d[N],f[N];
bool vs[N];
long long ssp(int s,int t)
{
long long cost=0;
while(1)
{
queue<int> q;
memset(d,0x3f,sizeof(d));
memset(vs,0,sizeof(vs));
d[s]=0; q.push(s); f[s]=inf;
while(!q.empty())
{
int u=q.front(); q.pop(); vs[u]=0;
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v,dis=d[u]+e[i].c;
if(dis<d[v]&&e[i].w)
{
d[v]=dis; f[v]=min(f[u],(long long)e[i].w); pre[v]=i;
if(!vs[v]) vs[v]=1,q.push(v);
}
}
}
if(d[t]>1e9) return cost;
cost+=d[t]*f[t];
for(int u=t;u!=s;u=e[pre[u]^1].v) e[pre[u]].w-=f[t],e[pre[u]^1].w+=f[t];
}
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
scanf("%d",&n); s=0; t=2*n+1;
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x);
if(i+1<=n)add(i,i+1,inf,0), add(i+1,i,0,0);
add(s,i,x,0); add(i,s,0,0);
add(i+n,t,x,0); add(t,i+n,0,0);
}
int p,d1,d2,v1,v2;
scanf("%d%d%d%d%d",&p,&d1,&v1,&d2,&v2);
for(int i=1;i<=n;i++)
{
add(s,i+n,inf,p); add(i+n,s,0,-p);
if(i+d1<=n) add(i,i+d1+n,inf,v1),add(i+d1+n,i,0,-v1);
if(i+d2<=n) add(i,i+d2+n,inf,v2),add(i+d2+n,i,0,-v2);
}
printf("%lld\n",ssp(s,t));
return 0;
}