终于意识到了一个好的网络流模板是有多么重要。%>_<%
而且上下界的代码细节特别多,再加上蒟蒻木有写一段就检查的好习惯,直接导致调了很久……
sgu194
这是一道无源点汇点的求上下界网络流的可行流。
首先把它转化为一个有源点汇点的网络流。令 du[u] 表示出边的下界之和减去入边的下界之和。把所有边的流量改为 up−down (上界-下界)。
建立一个超级源点 s 和超级汇点 t 。若点 u 的 du[u]>0 ,从 s 连一条边到 u 流量为 du[u] ;否则从 u 连一条边到 t 流量为 −du[u] 。之后做一次最大流。
如 s 的邻接边满流,说明该图的下界均能满足,则存在可行流。
#include <iostream>
#include <cstdio>
#include <queue>
#define MAXN 205
#define MAXM 40005
#define inf 2147483647
using namespace std;
int n, m, low[MAXM], flow[MAXM];
int a, b, e, du[MAXN];
struct node
{
int v, cap, back, next;
}edge[MAXM];
int adj[MAXN], pos;
inline void add(int a,int b,int c)
{
++pos;
edge[pos].v=b, edge[pos].cap=c, edge[pos].back=pos+1, edge[pos].next=adj[a];
adj[a]=pos;
++pos;
edge[pos].v=a, edge[pos].cap=0, edge[pos].back=pos-1, edge[pos].next=adj[b];
adj[b]=pos;
}
bool in[MAXN];
int d[MAXN], vd[MAXN];
void spfa()
{
for(int i=b;i<=e;++i)d[i]=inf;
queue<int>q;
q.push(e), d[e]=0;
int u, v;
while(!q.empty())
{
u=q.front(), in[u]=0;
q.pop();
for(int p=adj[u];p;p=edge[p].next)
if(edge[edge[p].back].cap>0&&d[(v=edge[p].v)]>d[u]+1)
{
d[v]=d[u]+1;
if(!in[v])
q.push(v), in[v]=1;
}
}
}
int aug(int u,int augco)
{
if(u==e)return augco;
int augc=augco, delta, v, mind=e+e;
for(int p=adj[u];p&&augc;p=edge[p].next)
{
if(edge[p].cap>0)
{
v=edge[p].v;
if(edge[p].cap>0&&d[u]==d[v]+1)
{
delta=aug(v,min(augc,edge[p].cap));
flow[p]+=delta, flow[edge[p].back]-=delta;
edge[p].cap-=delta, edge[edge[p].back].cap+=delta, augc-=delta;
if(d[b]>=e)break;
}
mind=min(mind,d[v]);
}
}
if(augco==augc)
{
--vd[d[u]];
if(!vd[d[u]])d[b]=e+1;
d[u]=mind+1;
++vd[d[u]];
}
return augco-augc;
}
void solve()
{
spfa();
for(int i=b;i<=e;++i)++vd[d[i]];
while(d[b]<=e)
aug(b,inf);
}
void work()
{
for(int p=adj[b];p;p=edge[p].next)
if(edge[p].cap>0)
{
puts("NO");
return;
}
puts("YES");
for(int i=1;i<=m;++i)
printf("%d\n",flow[i*2-1]+low[i]);
}
int main()
{
int d;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
scanf("%d%d%d%d",&a,&b,&low[i],&d);
du[a]-=low[i], du[b]+=low[i];
add(a,b,d-low[i]);
}
b=0, e=n+1;
for(int i=1;i<=n;++i)
if(du[i]>0)add(b,i,du[i]);
else add(i,e,-du[i]);
solve();
work();
return 0;
}
zoj3229
题目大意:有一个屌丝要给 m 个女神照相,分为 n 天。每个女神最多照 Gi 张照。第 i 天来了 C 个女神,第 T 个女神照的照片范围为 [L,R] ,而屌丝只能照 Di 张照片。求最多能照多少张照片。
这个建图应该比较容易吧~
一个源点连接 1−n 号点(表示第几天),边的上界为 Di 。 i 号点向 C 个女神连边,每一条边的上界为 R ,下界为 L 。 m 个女神向汇点连边,上界为 Gi 。
这就是一个有源点汇点的求最大流的问题。
首先要保证它有可行流。从汇点连一条边到源点,流量为 +∞ 。这就转化为一个无源点汇点。
求出可行流之后,删除超级源点和超级汇点,再做一次最大流即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 405
#define MAXM 1005
#define inf 0x3f3f3f3f
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n, m, adj[MAXN+MAXM], pos, low[MAXN][MAXM], eid[MAXN][MAXM];
int du[MAXN+MAXM], d[MAXN+MAXM], vd[MAXN+MAXM];
int b, e, a, c, l, k;
struct node
{
int v, cap, back, next;
}edge[MAXN*MAXM];
int flow[MAXN*MAXM];
inline void add(int a,int b,int c)
{
++pos;
edge[pos].v=b, edge[pos].cap=c;
edge[pos].back=pos+1, edge[pos].next=adj[a];
adj[a]=pos;
++pos;
edge[pos].v=a, edge[pos].cap=0;
edge[pos].back=pos-1, edge[pos].next=adj[b];
adj[b]=pos;
}
queue<int>q;
bool in[MAXN+MAXM];
void spfa()
{
memset(d,inf,sizeof d);
q.push(e), d[e]=0;
int u, v;
while(!q.empty())
{
u=q.front(), in[u]=0;
q.pop();
for(int p=adj[u];p;p=edge[p].next)
{
if(edge[edge[p].back].cap>=0&&d[(v=edge[p].v)]>d[u]+1)
{
d[v]=d[u]+1;
if(!in[v])q.push(v), in[v]=1;
}
}
}
}
int aug(int u,int augco)
{
if(u==e)return augco;
int augc=augco, delta, mind=e+e, v;
for(int p=adj[u];p&&augc;p=edge[p].next)
if(edge[p].cap>0)
{
v=edge[p].v;
if(d[v]+1==d[u])
{
delta=aug(v,min(augc,edge[p].cap));
flow[p]+=delta, flow[edge[p].back]-=delta;
edge[p].cap-=delta, edge[edge[p].back].cap+=delta, augc-=delta;
if(d[b]>e)break;
}
mind=min(mind,d[v]);
}
if(augco==augc)
{
--vd[d[u]];
if(!vd[d[u]])d[b]=e+1;
d[u]=mind+1;
++vd[d[u]];
}
return augco-augc;
}
int solve(int n,int m)
{
b=n, e=m;
spfa();
memset(vd,0,sizeof vd);
for(int i=0;i<=m;++i)++vd[d[i]];
int ans=0;
while(d[b]<=e)
ans+=aug(b,inf);
return ans;
}
inline void init()
{
memset(du,0,sizeof du);
memset(flow,0,sizeof flow);
memset(eid,0,sizeof eid), memset(low,0,sizeof low);
memset(adj,0,sizeof adj), pos=0;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
b=n+m, e=n+m+1;
for(int i=0;i<m;++i)
{
scanf("%d",&a);
du[e]+=a, du[i+n]-=a;
add(i+n,e,inf-a);
}
for(int i=0;i<n;++i)
{
scanf("%d%d",&k,&a);
add(b,i,a);
for(int j=0;j<k;++j)
{
scanf("%d%d%d",&a,&l,&c);
du[i]-=l, du[a+n]+=l, low[i][a]=l;
eid[i][a]=pos+1;
add(i,a+n,c-l);
}
}
add(e,b,inf);
b=e+1;
e=e+2;
int sum=0;
for(int i=0;i<=e-2;++i)
if(du[i]>0)
sum+=du[i], add(b,i,du[i]);
else
add(i,e,-du[i]);
if(sum!=solve(b,e))
printf("-1\n");
else
{
adj[b]=adj[e]=0;
e-=2, b=e-1;
adj[e]=edge[adj[e]].next;
int tmp=solve(b,e);
printf("%d\n",tmp);
for(int i=0;i<n;++i)
for(int j=0;j<m;++j)
if(eid[i][j])
printf("%d\n",flow[eid[i][j]]+low[i][j]);
}
puts("");
}
return 0;
}
sgu176
题目大意:在一个网络中有M条管道。若 k=1 ,该管道必须满流。若 k=0 ,则该管道的上界为 Z 。源点为 1 ,汇点为 n 。求此网络流中的最小流。
求最小流和最大流的方法差不多。只是先不连一条边(从汇点到源点的一条边,流量为 +∞ )。做一次最大流,再连上这条边。再做一次最大流。此时, t 到 s 的流量即是最小流。
第一次写,对边集数组的大小拿不准,RE了几次= =
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define inf 0x3f3f3f3f
#define MAXN 10005
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n, m, adj[MAXN], pos, a, b, c, e, k;
int d[MAXN], vd[MAXN];
int du[MAXN];
struct node
{
int v, cap, back, next, flow;
}edge[MAXN<<1];
int flow[MAXN], eid[MAXN];
inline void add(int a,int b,int c)
{
++pos;
edge[pos].v=b, edge[pos].cap=c, edge[pos].next=adj[a], edge[pos].back=pos+1;
adj[a]=pos;
++pos;
edge[pos].v=a, edge[pos].cap=0, edge[pos].next=adj[b], edge[pos].back=pos-1;
adj[b]=pos;
}
queue<int>q;
bool in[MAXN];
void spfa()
{
for(int i=1;i<=e;++i)d[i]=inf;
q.push(e), d[e]=0;
int u, v;
while(!q.empty())
{
u=q.front(), in[u]=0;
q.pop();
for(int p=adj[u];p;p=edge[p].next)
if(edge[edge[p].back].cap>0)
if(d[(v=edge[p].v)]>d[u]+1)
{
d[v]=d[u]+1;
if(!in[v])q.push(v), in[v]=1;
}
}
}
int aug(int u,int augco)
{
if(u==e)return augco;
int delta, augc=augco, mind=e+e, v;
for(int p=adj[u];p&&augc;p=edge[p].next)
if(edge[p].cap>0)
{
if(d[(v=edge[p].v)]+1==d[u])
{
delta=aug(v,min(edge[p].cap,augc));
augc-=delta, edge[p].cap-=delta, edge[edge[p].back].cap+=delta;
edge[p].flow+=delta, edge[edge[p].back].flow-=delta;
if(d[b]>e)break;
}
mind=min(mind,d[v]);
}
if(augco==augc)
{
--vd[d[u]];
if(!vd[d[u]])d[b]=e+1;
d[u]=mind+1;
++vd[d[u]];
}
return augco-augc;
}
int solve()
{
int ans=0;
spfa();
memset(vd,0,sizeof vd);
for(int i=1;i<=e;++i)
if(d[i]!=inf)++vd[d[i]];
while(d[b]<=e)
ans+=aug(b,inf);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
scanf("%d%d%d%d",&a,&b,&c,&k);
if(k)du[a]-=c, du[b]+=c, flow[i]=c;
else eid[i]=pos+1, add(a,b,c);
}
b=n+1, e=n+2;
for(int i=1;i<=n;++i)
if(du[i]>0)add(b,i,du[i]);
else add(i,e,-du[i]);
solve();
add(n,1,inf);
solve();
bool flag=0;
for(int p=adj[b];p;p=edge[p].next)
if(edge[p].cap>0)
{
flag=1;
break;
}
if(flag)puts("Impossible");
else
{
for(int p=adj[n];p;p=edge[p].next)
if(edge[p].v==1)
{
printf("%d\n",edge[p].flow);
break;
}
for(int i=1;i<m;++i)
printf("%d ",edge[eid[i]].flow+flow[i]);
printf("%d\n",edge[eid[m]].flow+flow[m]);
}
return 0;
}
zoj1994
题目大意:一个矩阵 n∗m ,已知每一列每一行的和。之后有 k 个限制条件。若有一个坐标为 0 ,则表示某一行或某一列的所有数都满足这个条件。求这个矩阵。
一开始我想的是建立 n∗m 个点,然后发现不好建图。
之后就想到的用第 i 行,第 j 列之间的流量来表示 (i,j) 这个点。
这就比较好建图了~(≧▽≦)/~
然后求一个可行流。
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define MAXN 305
#define inf 0x3f3f3f3f
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n, m, up[MAXN][MAXN], down[MAXN][MAXN];
int cap[MAXN][MAXN], flow[MAXN][MAXN], d[MAXN], vd[MAXN], du[MAXN];
int x, y, z, b, e, a, k, sum;
char w[5];
void work(int x,int y,char w)
{
if(w=='=')up[x][y+n]=down[x][y+n]=z;
else if(w=='>')down[x][y+n]=max(down[x][y+n],z+1);
else up[x][y+n]=min(up[x][y+n],z-1);
}
void build()
{
sum=0;
memset(down,0,sizeof down), memset(up,0,sizeof up);
memset(flow,0,sizeof flow), memset(cap,0,sizeof cap);
memset(du,0,sizeof du);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
up[i][j+n]=inf;
b=n+m+1, e=b+1;
for(int i=1;i<=n;++i)
{
scanf("%d",&a);
up[b][i]=down[b][i]=a;
}
for(int j=1;j<=m;++j)
{
scanf("%d",&a);
up[j+n][e]=down[j+n][e]=a;
}
scanf("%d",&k);
while(k--)
{
scanf("%d%d%s%d",&x,&y,w,&z);
if(!x&&!y)
{
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
work(i,j,w[0]);
}
else if(!x&&y)
for(int i=1;i<=n;++i)
work(i,y,w[0]);
else if(x&&!y)
for(int j=1;j<=m;++j)
work(x,j,w[0]);
else work(x,y,w[0]);
}
for(int i=1;i<=e;++i)
for(int j=1;j<=e;++j)
{
cap[i][j]=up[i][j]-down[i][j];
du[i]-=down[i][j], du[j]+=down[i][j];
}
b=e+1, e=b+1;
for(int i=1;i<b;++i)
if(du[i]>0)cap[b][i]=du[i], sum+=du[i];
else cap[i][e]=-du[i];
}
bool in[MAXN];
void spfa()
{
memset(d,inf,sizeof d);
queue<int>q;
d[e]=0, q.push(e);
int u;
while(!q.empty())
{
u=q.front(), in[u]=0;
q.pop();
for(int i=1;i<=e;++i)
if(cap[i][u]>0&&d[i]>d[u]+1)
{
d[i]=d[u]+1;
if(!in[i])in[i]=1, q.push(i);
}
}
}
int aug(int u,int augco)
{
if(u==e)return augco;
int delta, mind=e+e, augc=augco;
for(int i=1;i<=e&&augc;++i)
if(cap[u][i]>0)
{
mind=min(mind,d[i]);
if(d[i]+1==d[u])
{
delta=aug(i,min(cap[u][i],augc));
augc-=delta, cap[u][i]-=delta, cap[i][u]+=delta;
flow[u][i]+=delta, flow[i][u]-=delta;
if(d[b]>e)break;
}
}
if(augc==augco)
{
--vd[d[u]];
if(!vd[d[u]])d[b]=e+1;
d[u]=mind+1;
++vd[d[u]];
}
return augco-augc;
}
int solve()
{
int ans=0;
spfa();
memset(vd,0,sizeof vd);
for(int i=1;i<=e;++i)
if(d[i]!=inf)++vd[d[i]];
while(d[b]<=e)
ans+=aug(b,inf);
return ans;
}
int main()
{
int cas, ans;
scanf("%d",&cas);
while(cas--)
{
build();
cap[n+m+2][n+m+1]=inf;
ans=solve();
if(ans!=sum)
{
puts("IMPOSSIBLE");
continue;
}
cap[n+m+1][n+m+2]=cap[n+m+2][n+m+1]=0;
solve();
for(int i=1;i<=n;++i)
{
for(int j=1;j<m;++j)
printf("%d ",flow[i][j+n]+down[i][j+n]);
printf("%d\n",flow[i][m+n]+down[i][m+n]);
}
puts("");
}
return 0;
}
zoj3496
题目大意:有一个网络流。有 P 个技能点,在某一条边上使用 i 次,表示该边的费用为 i 。
有两种情况。第一种,在保证最大流的情况下,A想话费最少,B想收费最多。第二种,在保证最大流的情况下,A想话费最多,B想收费最少。输出这两个答案。
这个技能点的使用肯定是贪心,即全部点在一条边上…
一开始就想到了二分,然而只二分了第二个解…然后就木有搞出来…
然后搜了下题解,两个都要二分…
对于第一个解,实际上是限制上界。对于第二个解,实际上是限制下界。
顿时就变成赤裸裸的大水体…
然而蒟蒻把最大流的总点数弄错了,T了好多次…%>_<%
其实这个网络流的模板效率还可以…
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long int
#define MAXN 605
#define MAXM 20005
#define inf 0x3f3f3f3f
using namespace std;
int n, m, s, t, S, T, adj[MAXN], End;
LL sum;
int a[MAXM], b[MAXM], c[MAXM], maxc, pos, maxflow;
int d[MAXN], du[MAXN], vd[MAXN];
struct node
{
int v, next, cap;
}edge[MAXM<<1];
inline void add(int a,int b,int c)
{
edge[pos].v=b, edge[pos].cap=c, edge[pos].next=adj[a];
adj[a]=pos;
++pos;
edge[pos].v=a, edge[pos].cap=0, edge[pos].next=adj[b];
adj[b]=pos;
++pos;
}
queue<int>q;
bool in[MAXN];
void spfa()
{
for(int i=0;i<t;++i)d[i]=inf;
d[t]=0, q.push(t);
int u, v;
while(!q.empty())
{
u=q.front(), in[u]=0;
q.pop();
for(int p=adj[u];p!=-1;p=edge[p].next)
if(edge[p^1].cap>0)
if(d[(v=edge[p].v)]>d[u]+1)
{
d[v]=d[u]+1;
if(!in[v])in[v]=1, q.push(v);
}
}
}
int aug(int u,int augco)
{
if(u==t)return augco;
int augc=augco, delta, v, mind=End-1;
for(int p=adj[u];p!=-1&&augc;p=edge[p].next)
if(edge[p].cap>0)
{
if(d[(v=edge[p].v)]+1==d[u])
{
delta=aug(v,min(augc,edge[p].cap));
edge[p].cap-=delta, edge[p^1].cap+=delta, augc-=delta;
if(d[s]>=End)break;
}
mind=min(mind,d[v]);
}
if(augco==augc)
{
--vd[d[u]];
if(!vd[d[u]])d[s]=End;
d[u]=mind+1;
++vd[d[u]];
}
return augco-augc;
}
int isap(int a,int b,int c)
{
s=a, t=b, End=c;
spfa();
memset(vd,0,sizeof vd);
for(int i=0;i<=c;++i)
if(d[i]!=inf)++vd[d[i]];
int ans=0;
while(d[s]<End)
ans+=aug(s,inf);
return ans;
}
bool check1(int mid)
{
memset(adj,-1,sizeof adj), pos=0;
for(int i=0;i<m;++i)
add(a[i],b[i],min(mid,c[i]));
return isap(S,T,n)==maxflow;
}
LL solve1()
{
int l=0, r=maxc, mid;
LL ans=0;
while(l<=r)
{
mid=(l+r)/2;
if(check1(mid))
ans=mid, r=mid-1;
else l=mid+1;
}
return ans*sum;
}
bool check2(int mid)
{
memset(adj,-1,sizeof adj), memset(du,0,sizeof du);
pos=0;
for(int i=0;i<m;++i)
{
if(c[i]<mid)return 0;
add(a[i],b[i],c[i]-mid);
du[a[i]]-=mid, du[b[i]]+=mid;
}
s=n, t=n+1;
for(int i=0;i<n;++i)
if(du[i]>0)add(s,i,du[i]);
else if(du[i]<0)add(i,t,-du[i]);
add(T,S,inf);
isap(s,t,t+1);
for(int p=adj[s];p!=-1;p=edge[p].next)
if(edge[p].cap)return 0;
return isap(S,T,t+1)==maxflow;
}
LL solve2()
{
int l=0, r=maxc, mid;
LL ans=0;
while(l<=r)
{
mid=(l+r)/2;
if(check2(mid))ans=mid, l=mid+1;
else r=mid-1;
}
return ans*sum;
}
int main()
{
int cas;
scanf("%d",&cas);
while(cas--)
{
scanf("%d%d%d%d%lld",&n,&m,&S,&T,&sum);
memset(adj,-1,sizeof adj);
maxc=0, pos=0;
for(int i=0;i<m;++i)
{
scanf("%d%d%d",&a[i],&b[i],&c[i]);
maxc=max(c[i],maxc);
add(a[i],b[i],c[i]);
}
maxflow=isap(S,T,n);
printf("%lld %lld\n",solve1(),solve2());
}
return 0;
}